diff --git a/.ci.settings.py b/.ci.settings.py
index 637c918399..49b693ef46 100644
--- a/.ci.settings.py
+++ b/.ci.settings.py
@@ -2,11 +2,7 @@
STATICFILES_FINDERS += ('compressor.finders.CompressorFinder',)
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
-CACHES = {
- 'default': {
- 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
- }
-}
+CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}
DATABASES = {
'default': {
diff --git a/django_ace/widgets.py b/django_ace/widgets.py
index 2f521bf7f7..c0b90d7abd 100644
--- a/django_ace/widgets.py
+++ b/django_ace/widgets.py
@@ -11,8 +11,17 @@
class AceWidget(forms.Textarea):
- def __init__(self, mode=None, theme=None, wordwrap=False, width='100%', height='300px',
- no_ace_media=False, *args, **kwargs):
+ def __init__(
+ self,
+ mode=None,
+ theme=None,
+ wordwrap=False,
+ width='100%',
+ height='300px',
+ no_ace_media=False,
+ *args,
+ **kwargs
+ ):
self.mode = mode
self.theme = theme
self.wordwrap = wordwrap
@@ -47,13 +56,17 @@ def render(self, name, value, attrs=None, renderer=None):
if self.wordwrap:
ace_attrs['data-wordwrap'] = 'true'
- attrs.update(style='width: 100%; min-width: 100%; max-width: 100%; resize: none')
+ attrs.update(
+ style='width: 100%; min-width: 100%; max-width: 100%; resize: none'
+ )
textarea = super(AceWidget, self).render(name, value, attrs)
html = '
%s' % (flatatt(ace_attrs), textarea)
# add toolbar
- html = ('') % html
+ html = (
+ ''
+ ) % html
return mark_safe(html)
diff --git a/dmoj/celery.py b/dmoj/celery.py
index e1da640642..7f7ac1a0fd 100644
--- a/dmoj/celery.py
+++ b/dmoj/celery.py
@@ -7,6 +7,7 @@
app = Celery('dmoj')
from django.conf import settings # noqa: E402, I202, django must be imported here
+
app.config_from_object(settings, namespace='CELERY')
if hasattr(settings, 'CELERY_BROKER_URL_SECRET'):
@@ -23,5 +24,10 @@
@task_failure.connect()
def celery_failure_log(sender, task_id, exception, traceback, *args, **kwargs):
- logger.error('Celery Task %s: %s on %s', sender.name, task_id, socket.gethostname(), # noqa: G201
- exc_info=(type(exception), exception, traceback))
+ logger.error(
+ 'Celery Task %s: %s on %s',
+ sender.name,
+ task_id,
+ socket.gethostname(), # noqa: G201
+ exc_info=(type(exception), exception, traceback),
+ )
diff --git a/dmoj/settings.py b/dmoj/settings.py
index cea555a59e..231104c45e 100644
--- a/dmoj/settings.py
+++ b/dmoj/settings.py
@@ -46,7 +46,7 @@
# Refer to https://dmoj.ca/post/103-point-system-rework
DMOJ_PP_STEP = 0.95
DMOJ_PP_ENTRIES = 100
-DMOJ_PP_BONUS_FUNCTION = lambda n: 300 * (1 - 0.997 ** n) # noqa: E731
+DMOJ_PP_BONUS_FUNCTION = lambda n: 300 * (1 - 0.997**n) # noqa: E731
ACE_URL = '//cdnjs.cloudflare.com/ajax/libs/ace/1.1.3'
SELECT2_JS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js'
@@ -64,11 +64,26 @@
DMOJ_PROBLEM_MIN_MEMORY_LIMIT = 0 # kilobytes
DMOJ_PROBLEM_MAX_MEMORY_LIMIT = 1048576 # kilobytes
DMOJ_PROBLEM_MIN_PROBLEM_POINTS = 0
-DMOJ_PROBLEM_MIN_USER_POINTS_VOTE = 1 # when voting on problem, minimum point value user can select
-DMOJ_PROBLEM_MAX_USER_POINTS_VOTE = 50 # when voting on problem, maximum point value user can select
+DMOJ_PROBLEM_MIN_USER_POINTS_VOTE = (
+ 1 # when voting on problem, minimum point value user can select
+)
+DMOJ_PROBLEM_MAX_USER_POINTS_VOTE = (
+ 50 # when voting on problem, maximum point value user can select
+)
DMOJ_PROBLEM_HOT_PROBLEM_COUNT = 7
-DMOJ_PROBLEM_STATEMENT_DISALLOWED_CHARACTERS = {'“', '”', '‘', '’', '−', 'ff', 'fi', 'fl', 'ffi', 'ffl'}
+DMOJ_PROBLEM_STATEMENT_DISALLOWED_CHARACTERS = {
+ '“',
+ '”',
+ '‘',
+ '’',
+ '−',
+ 'ff',
+ 'fi',
+ 'fl',
+ 'ffi',
+ 'ffl',
+}
DMOJ_RATING_COLORS = True
DMOJ_EMAIL_THROTTLING = (10, 60)
@@ -158,7 +173,9 @@
INLINE_JQUERY = True
INLINE_FONTAWESOME = True
JQUERY_JS = '//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js'
-FONTAWESOME_CSS = '//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css'
+FONTAWESOME_CSS = (
+ '//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css'
+)
DMOJ_CANONICAL = ''
# Application definition
@@ -358,7 +375,8 @@
'trim_blocks': True,
'lstrip_blocks': True,
'translation_engine': 'judge.utils.safe_translations',
- 'extensions': DEFAULT_EXTENSIONS + [
+ 'extensions': DEFAULT_EXTENSIONS
+ + [
'compressor.contrib.jinja2ext.CompressorExtension',
'judge.jinja2.DMOJExtension',
'judge.jinja2.spaceless.SpacelessExtension',
@@ -410,15 +428,76 @@
]
BLEACH_USER_SAFE_TAGS = [
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
- 'b', 'i', 'strong', 'em', 'tt', 'del', 'kbd', 's', 'abbr', 'cite', 'mark', 'q', 'samp', 'small',
- 'u', 'var', 'wbr', 'dfn', 'ruby', 'rb', 'rp', 'rt', 'rtc', 'sub', 'sup', 'time', 'data',
- 'p', 'br', 'pre', 'span', 'div', 'blockquote', 'code', 'hr',
- 'ul', 'ol', 'li', 'dd', 'dl', 'dt', 'address', 'section', 'details', 'summary',
- 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption', 'colgroup', 'col', 'tfoot',
- 'img', 'audio', 'video', 'source',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'b',
+ 'i',
+ 'strong',
+ 'em',
+ 'tt',
+ 'del',
+ 'kbd',
+ 's',
+ 'abbr',
+ 'cite',
+ 'mark',
+ 'q',
+ 'samp',
+ 'small',
+ 'u',
+ 'var',
+ 'wbr',
+ 'dfn',
+ 'ruby',
+ 'rb',
+ 'rp',
+ 'rt',
+ 'rtc',
+ 'sub',
+ 'sup',
+ 'time',
+ 'data',
+ 'p',
+ 'br',
+ 'pre',
+ 'span',
+ 'div',
+ 'blockquote',
+ 'code',
+ 'hr',
+ 'ul',
+ 'ol',
+ 'li',
+ 'dd',
+ 'dl',
+ 'dt',
+ 'address',
+ 'section',
+ 'details',
+ 'summary',
+ 'table',
+ 'thead',
+ 'tbody',
+ 'tfoot',
+ 'tr',
+ 'th',
+ 'td',
+ 'caption',
+ 'colgroup',
+ 'col',
+ 'tfoot',
+ 'img',
+ 'audio',
+ 'video',
+ 'source',
'a',
- 'style', 'noscript', 'center',
+ 'style',
+ 'noscript',
+ 'center',
]
BLEACH_USER_SAFE_ATTRS = {
@@ -432,7 +511,18 @@
'td': ['colspan', 'rowspan'],
'th': ['colspan', 'rowspan'],
'audio': ['autoplay', 'controls', 'crossorigin', 'muted', 'loop', 'preload', 'src'],
- 'video': ['autoplay', 'controls', 'crossorigin', 'height', 'muted', 'loop', 'poster', 'preload', 'src', 'width'],
+ 'video': [
+ 'autoplay',
+ 'controls',
+ 'crossorigin',
+ 'height',
+ 'muted',
+ 'loop',
+ 'poster',
+ 'preload',
+ 'src',
+ 'width',
+ ],
'source': ['src', 'srcset', 'type'],
'li': ['value'],
}
@@ -531,7 +621,9 @@
EVENT_DAEMON_POLL = '/channels/'
EVENT_DAEMON_KEY = None
EVENT_DAEMON_AMQP_EXCHANGE = 'dmoj-events'
-EVENT_DAEMON_SUBMISSION_KEY = '6Sdmkx^%pk@GsifDfXcwX*Y7LRF%RGT8vmFpSxFBT$fwS7trc8raWfN#CSfQuKApx&$B#Gh2L7p%W!Ww'
+EVENT_DAEMON_SUBMISSION_KEY = (
+ '6Sdmkx^%pk@GsifDfXcwX*Y7LRF%RGT8vmFpSxFBT$fwS7trc8raWfN#CSfQuKApx&$B#Gh2L7p%W!Ww'
+)
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
diff --git a/dmoj/urls.py b/dmoj/urls.py
index 08b7812ed7..2976a6a716 100644
--- a/dmoj/urls.py
+++ b/dmoj/urls.py
@@ -10,69 +10,164 @@
from django.views.generic import RedirectView
from martor.views import markdown_search_user
-from judge.feed import AtomBlogFeed, AtomCommentFeed, AtomProblemFeed, BlogFeed, CommentFeed, ProblemFeed
+from judge.feed import (
+ AtomBlogFeed,
+ AtomCommentFeed,
+ AtomProblemFeed,
+ BlogFeed,
+ CommentFeed,
+ ProblemFeed,
+)
from judge.sitemap import sitemaps
-from judge.views import TitledTemplateView, api, blog, comment, contests, language, license, mailgun, organization, \
- preview, problem, problem_manage, ranked_submission, register, stats, status, submission, tasks, ticket, \
- two_factor, user, widgets
-from judge.views.problem_data import ProblemDataView, ProblemSubmissionDiff, \
- problem_data_file, problem_init_view
+from judge.views import (
+ TitledTemplateView,
+ api,
+ blog,
+ comment,
+ contests,
+ language,
+ license,
+ mailgun,
+ organization,
+ preview,
+ problem,
+ problem_manage,
+ ranked_submission,
+ register,
+ stats,
+ status,
+ submission,
+ tasks,
+ ticket,
+ two_factor,
+ user,
+ widgets,
+)
+from judge.views.problem_data import (
+ ProblemDataView,
+ ProblemSubmissionDiff,
+ problem_data_file,
+ problem_init_view,
+)
from judge.views.register import ActivationView, RegistrationView
-from judge.views.select2 import AssigneeSelect2View, ClassSelect2View, CommentSelect2View, ContestSelect2View, \
- ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \
- UserSearchSelect2View, UserSelect2View
+from judge.views.select2 import (
+ AssigneeSelect2View,
+ ClassSelect2View,
+ CommentSelect2View,
+ ContestSelect2View,
+ ContestUserSearchSelect2View,
+ OrganizationSelect2View,
+ ProblemSelect2View,
+ TicketUserSelect2View,
+ UserSearchSelect2View,
+ UserSelect2View,
+)
from judge.views.widgets import martor_image_uploader
admin.autodiscover()
register_patterns = [
- path('activate/complete/',
- TitledTemplateView.as_view(template_name='registration/activation_complete.html',
- title=_('Activation Successful!')),
- name='registration_activation_complete'),
+ path(
+ 'activate/complete/',
+ TitledTemplateView.as_view(
+ template_name='registration/activation_complete.html',
+ title=_('Activation Successful!'),
+ ),
+ name='registration_activation_complete',
+ ),
# Let's use , because a bad activation key should still get to the view;
# that way, it can return a sensible "invalid key" message instead of a confusing 404.
- path('activate//', ActivationView.as_view(), name='registration_activate'),
+ path(
+ 'activate//',
+ ActivationView.as_view(),
+ name='registration_activate',
+ ),
path('register/', RegistrationView.as_view(), name='registration_register'),
- path('register/complete/',
- TitledTemplateView.as_view(template_name='registration/registration_complete.html',
- title=_('Registration Completed')),
- name='registration_complete'),
- path('register/closed/',
- TitledTemplateView.as_view(template_name='registration/registration_closed.html',
- title=_('Registration Not Allowed')),
- name='registration_disallowed'),
+ path(
+ 'register/complete/',
+ TitledTemplateView.as_view(
+ template_name='registration/registration_complete.html',
+ title=_('Registration Completed'),
+ ),
+ name='registration_complete',
+ ),
+ path(
+ 'register/closed/',
+ TitledTemplateView.as_view(
+ template_name='registration/registration_closed.html',
+ title=_('Registration Not Allowed'),
+ ),
+ name='registration_disallowed',
+ ),
path('login/', user.CustomLoginView.as_view(), name='auth_login'),
path('logout/', user.UserLogoutView.as_view(), name='auth_logout'),
- path('password/change/', user.CustomPasswordChangeView.as_view(), name='password_change'),
- path('password/change/done/', auth_views.PasswordChangeDoneView.as_view(
- template_name='registration/password_change_done.html',
- ), name='password_change_done'),
- path('password/reset/', user.CustomPasswordResetView.as_view(), name='password_reset'),
- re_path(r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$',
- auth_views.PasswordResetConfirmView.as_view(
- template_name='registration/password_reset_confirm.html',
- ), name='password_reset_confirm'),
- path('password/reset/complete/', auth_views.PasswordResetCompleteView.as_view(
- template_name='registration/password_reset_complete.html',
- ), name='password_reset_complete'),
- path('password/reset/done/', auth_views.PasswordResetDoneView.as_view(
- template_name='registration/password_reset_done.html',
- ), name='password_reset_done'),
+ path(
+ 'password/change/',
+ user.CustomPasswordChangeView.as_view(),
+ name='password_change',
+ ),
+ path(
+ 'password/change/done/',
+ auth_views.PasswordChangeDoneView.as_view(
+ template_name='registration/password_change_done.html',
+ ),
+ name='password_change_done',
+ ),
+ path(
+ 'password/reset/', user.CustomPasswordResetView.as_view(), name='password_reset'
+ ),
+ re_path(
+ r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$',
+ auth_views.PasswordResetConfirmView.as_view(
+ template_name='registration/password_reset_confirm.html',
+ ),
+ name='password_reset_confirm',
+ ),
+ path(
+ 'password/reset/complete/',
+ auth_views.PasswordResetCompleteView.as_view(
+ template_name='registration/password_reset_complete.html',
+ ),
+ name='password_reset_complete',
+ ),
+ path(
+ 'password/reset/done/',
+ auth_views.PasswordResetDoneView.as_view(
+ template_name='registration/password_reset_done.html',
+ ),
+ name='password_reset_done',
+ ),
path('social/error/', register.social_auth_error, name='social_auth_error'),
path('email/change/', user.EmailChangeRequestView.as_view(), name='email_change'),
- path('email/change/activate//',
- user.EmailChangeActivateView.as_view(), name='email_change_activate'),
-
+ path(
+ 'email/change/activate//',
+ user.EmailChangeActivateView.as_view(),
+ name='email_change_activate',
+ ),
path('2fa/', two_factor.TwoFactorLoginView.as_view(), name='login_2fa'),
path('2fa/enable/', two_factor.TOTPEnableView.as_view(), name='enable_2fa'),
path('2fa/edit/', two_factor.TOTPEditView.as_view(), name='edit_2fa'),
path('2fa/disable/', two_factor.TOTPDisableView.as_view(), name='disable_2fa'),
- path('2fa/webauthn/attest/', two_factor.WebAuthnAttestationView.as_view(), name='webauthn_attest'),
- path('2fa/webauthn/assert/', two_factor.WebAuthnAttestView.as_view(), name='webauthn_assert'),
- path('2fa/webauthn/delete/', two_factor.WebAuthnDeleteView.as_view(), name='webauthn_delete'),
- path('2fa/scratchcode/generate/', user.generate_scratch_codes, name='generate_scratch_codes'),
-
+ path(
+ '2fa/webauthn/attest/',
+ two_factor.WebAuthnAttestationView.as_view(),
+ name='webauthn_attest',
+ ),
+ path(
+ '2fa/webauthn/assert/',
+ two_factor.WebAuthnAttestView.as_view(),
+ name='webauthn_assert',
+ ),
+ path(
+ '2fa/webauthn/delete/',
+ two_factor.WebAuthnDeleteView.as_view(),
+ name='webauthn_delete',
+ ),
+ path(
+ '2fa/scratchcode/generate/',
+ user.generate_scratch_codes,
+ name='generate_scratch_codes',
+ ),
path('api/token/generate/', user.generate_api_token, name='generate_api_token'),
path('api/token/remove/', user.remove_api_token, name='remove_api_token'),
]
@@ -85,305 +180,782 @@ def exception(request):
def paged_list_view(view, name):
- return include([
- path('', view.as_view(), name=name),
- path('', view.as_view(), name=name),
- ])
+ return include(
+ [
+ path('', view.as_view(), name=name),
+ path('', view.as_view(), name=name),
+ ]
+ )
urlpatterns = [
- path('', blog.PostList.as_view(template_name='home.html', title=_('Home')), kwargs={'page': 1}, name='home'),
+ path(
+ '',
+ blog.PostList.as_view(template_name='home.html', title=_('Home')),
+ kwargs={'page': 1},
+ name='home',
+ ),
path('500/', exception),
path('admin/', admin.site.urls),
path('i18n/', include('django.conf.urls.i18n')),
path('accounts/', include(register_patterns)),
path('', include('social_django.urls')),
-
path('problems/', problem.ProblemList.as_view(), name='problem_list'),
path('problems/random/', problem.RandomProblem.as_view(), name='problem_random'),
-
- path('problem/', include([
- path('', problem.ProblemDetail.as_view(), name='problem_detail'),
- path('/editorial', problem.ProblemSolution.as_view(), name='problem_editorial'),
- path('/pdf', problem.ProblemPdfView.as_view(), name='problem_pdf'),
- path('/pdf/', problem.ProblemPdfView.as_view(), name='problem_pdf'),
- path('/clone', problem.ProblemClone.as_view(), name='problem_clone'),
- path('/submit', problem.ProblemSubmit.as_view(), name='problem_submit'),
- path('/resubmit/', problem.ProblemSubmit.as_view(), name='problem_submit'),
-
- path('/rank/', paged_list_view(ranked_submission.RankedSubmissions, 'ranked_submissions')),
- path('/submissions/', paged_list_view(submission.ProblemSubmissions, 'chronological_submissions')),
- path('/submissions//', paged_list_view(submission.UserProblemSubmissions, 'user_submissions')),
-
- path('/', lambda _, problem: HttpResponsePermanentRedirect(reverse('problem_detail', args=[problem]))),
-
- path('/test_data', ProblemDataView.as_view(), name='problem_data'),
- path('/test_data/init', problem_init_view, name='problem_data_init'),
- path('/test_data/diff', ProblemSubmissionDiff.as_view(), name='problem_submission_diff'),
- path('/data/', problem_data_file, name='problem_data_file'),
-
- path('/tickets', ticket.ProblemTicketListView.as_view(), name='problem_ticket_list'),
- path('/tickets/new', ticket.NewProblemTicketView.as_view(), name='new_problem_ticket'),
-
- path('/vote', problem.ProblemVote.as_view(), name='problem_vote'),
- path('/vote/delete', problem.DeleteProblemVote.as_view(), name='delete_problem_vote'),
- path('/vote/stats', problem.ProblemVoteStats.as_view(), name='problem_vote_stats'),
-
- path('/manage/submission', include([
- path('', problem_manage.ManageProblemSubmissionView.as_view(), name='problem_manage_submissions'),
- path('/rejudge', problem_manage.RejudgeSubmissionsView.as_view(), name='problem_submissions_rejudge'),
- path('/rejudge/preview', problem_manage.PreviewRejudgeSubmissionsView.as_view(),
- name='problem_submissions_rejudge_preview'),
- path('/rejudge/success/', problem_manage.rejudge_success,
- name='problem_submissions_rejudge_success'),
- path('/rescore/all', problem_manage.RescoreAllSubmissionsView.as_view(),
- name='problem_submissions_rescore_all'),
- path('/rescore/success/', problem_manage.rescore_success,
- name='problem_submissions_rescore_success'),
- ])),
- ])),
-
+ path(
+ 'problem/',
+ include(
+ [
+ path('', problem.ProblemDetail.as_view(), name='problem_detail'),
+ path(
+ '/editorial',
+ problem.ProblemSolution.as_view(),
+ name='problem_editorial',
+ ),
+ path('/pdf', problem.ProblemPdfView.as_view(), name='problem_pdf'),
+ path(
+ '/pdf/',
+ problem.ProblemPdfView.as_view(),
+ name='problem_pdf',
+ ),
+ path('/clone', problem.ProblemClone.as_view(), name='problem_clone'),
+ path('/submit', problem.ProblemSubmit.as_view(), name='problem_submit'),
+ path(
+ '/resubmit/',
+ problem.ProblemSubmit.as_view(),
+ name='problem_submit',
+ ),
+ path(
+ '/rank/',
+ paged_list_view(
+ ranked_submission.RankedSubmissions, 'ranked_submissions'
+ ),
+ ),
+ path(
+ '/submissions/',
+ paged_list_view(
+ submission.ProblemSubmissions, 'chronological_submissions'
+ ),
+ ),
+ path(
+ '/submissions//',
+ paged_list_view(
+ submission.UserProblemSubmissions, 'user_submissions'
+ ),
+ ),
+ path(
+ '/',
+ lambda _, problem: HttpResponsePermanentRedirect(
+ reverse('problem_detail', args=[problem])
+ ),
+ ),
+ path('/test_data', ProblemDataView.as_view(), name='problem_data'),
+ path('/test_data/init', problem_init_view, name='problem_data_init'),
+ path(
+ '/test_data/diff',
+ ProblemSubmissionDiff.as_view(),
+ name='problem_submission_diff',
+ ),
+ path('/data/', problem_data_file, name='problem_data_file'),
+ path(
+ '/tickets',
+ ticket.ProblemTicketListView.as_view(),
+ name='problem_ticket_list',
+ ),
+ path(
+ '/tickets/new',
+ ticket.NewProblemTicketView.as_view(),
+ name='new_problem_ticket',
+ ),
+ path('/vote', problem.ProblemVote.as_view(), name='problem_vote'),
+ path(
+ '/vote/delete',
+ problem.DeleteProblemVote.as_view(),
+ name='delete_problem_vote',
+ ),
+ path(
+ '/vote/stats',
+ problem.ProblemVoteStats.as_view(),
+ name='problem_vote_stats',
+ ),
+ path(
+ '/manage/submission',
+ include(
+ [
+ path(
+ '',
+ problem_manage.ManageProblemSubmissionView.as_view(),
+ name='problem_manage_submissions',
+ ),
+ path(
+ '/rejudge',
+ problem_manage.RejudgeSubmissionsView.as_view(),
+ name='problem_submissions_rejudge',
+ ),
+ path(
+ '/rejudge/preview',
+ problem_manage.PreviewRejudgeSubmissionsView.as_view(),
+ name='problem_submissions_rejudge_preview',
+ ),
+ path(
+ '/rejudge/success/',
+ problem_manage.rejudge_success,
+ name='problem_submissions_rejudge_success',
+ ),
+ path(
+ '/rescore/all',
+ problem_manage.RescoreAllSubmissionsView.as_view(),
+ name='problem_submissions_rescore_all',
+ ),
+ path(
+ '/rescore/success/',
+ problem_manage.rescore_success,
+ name='problem_submissions_rescore_success',
+ ),
+ ]
+ ),
+ ),
+ ]
+ ),
+ ),
path('submissions/', paged_list_view(submission.AllSubmissions, 'all_submissions')),
- path('submissions/user//', paged_list_view(submission.AllUserSubmissions, 'all_user_submissions')),
-
- path('src/', submission.SubmissionSource.as_view(), name='submission_source'),
- path('src//raw', submission.SubmissionSourceRaw.as_view(), name='submission_source_raw'),
-
- path('submission/', include([
- path('', submission.SubmissionStatus.as_view(), name='submission_status'),
- path('/abort', submission.abort_submission, name='submission_abort'),
- ])),
-
- path('users/', include([
- path('', user.users, name='user_list'),
- path('', lambda request, page:
- HttpResponsePermanentRedirect('%s?page=%s' % (reverse('user_list'), page))),
- path('find', user.user_ranking_redirect, name='user_ranking_redirect'),
- ])),
-
+ path(
+ 'submissions/user//',
+ paged_list_view(submission.AllUserSubmissions, 'all_user_submissions'),
+ ),
+ path(
+ 'src/',
+ submission.SubmissionSource.as_view(),
+ name='submission_source',
+ ),
+ path(
+ 'src//raw',
+ submission.SubmissionSourceRaw.as_view(),
+ name='submission_source_raw',
+ ),
+ path(
+ 'submission/',
+ include(
+ [
+ path(
+ '', submission.SubmissionStatus.as_view(), name='submission_status'
+ ),
+ path('/abort', submission.abort_submission, name='submission_abort'),
+ ]
+ ),
+ ),
+ path(
+ 'users/',
+ include(
+ [
+ path('', user.users, name='user_list'),
+ path(
+ '',
+ lambda request, page: HttpResponsePermanentRedirect(
+ '%s?page=%s' % (reverse('user_list'), page)
+ ),
+ ),
+ path('find', user.user_ranking_redirect, name='user_ranking_redirect'),
+ ]
+ ),
+ ),
path('user', user.UserDashboard.as_view(), name='user_dashboard'),
path('edit/profile/', user.edit_profile, name='user_edit_profile'),
path('data/prepare/', user.UserPrepareData.as_view(), name='user_prepare_data'),
path('data/download/', user.UserDownloadData.as_view(), name='user_download_data'),
- path('user/', include([
- path('', user.UserDashboard.as_view(), name='user_dashboard'),
- path('/solved', include([
- path('', user.UserProblemsPage.as_view(), name='user_problems'),
- path('/ajax', user.UserPerformancePointsAjax.as_view(), name='user_pp_ajax'),
- ])),
- path('/submissions/', paged_list_view(submission.AllUserSubmissions, 'all_user_submissions_old')),
- path('/submissions/', lambda _, user:
- HttpResponsePermanentRedirect(reverse('all_user_submissions', args=[user]))),
-
- path('/', lambda _, user: HttpResponsePermanentRedirect(reverse('user_dashboard', args=[user]))),
- ])),
-
+ path(
+ 'user/',
+ include(
+ [
+ path('', user.UserDashboard.as_view(), name='user_dashboard'),
+ path(
+ '/solved',
+ include(
+ [
+ path(
+ '',
+ user.UserProblemsPage.as_view(),
+ name='user_problems',
+ ),
+ path(
+ '/ajax',
+ user.UserPerformancePointsAjax.as_view(),
+ name='user_pp_ajax',
+ ),
+ ]
+ ),
+ ),
+ path(
+ '/submissions/',
+ paged_list_view(
+ submission.AllUserSubmissions, 'all_user_submissions_old'
+ ),
+ ),
+ path(
+ '/submissions/',
+ lambda _, user: HttpResponsePermanentRedirect(
+ reverse('all_user_submissions', args=[user])
+ ),
+ ),
+ path(
+ '/',
+ lambda _, user: HttpResponsePermanentRedirect(
+ reverse('user_dashboard', args=[user])
+ ),
+ ),
+ ]
+ ),
+ ),
path('comments/upvote/', comment.upvote_comment, name='comment_upvote'),
path('comments/downvote/', comment.downvote_comment, name='comment_downvote'),
path('comments/hide/', comment.comment_hide, name='comment_hide'),
- path('comments//', include([
- path('edit', comment.CommentEdit.as_view(), name='comment_edit'),
- path('history/ajax', comment.CommentRevisionAjax.as_view(), name='comment_revision_ajax'),
- path('edit/ajax', comment.CommentEditAjax.as_view(), name='comment_edit_ajax'),
- path('votes/ajax', comment.CommentVotesAjax.as_view(), name='comment_votes_ajax'),
- path('render', comment.CommentContent.as_view(), name='comment_content'),
- ])),
-
- path('contests/',contests.ContestList.as_view(), name='contest_list'), # if broken add $ to end of regex and make regex
+ path(
+ 'comments//',
+ include(
+ [
+ path('edit', comment.CommentEdit.as_view(), name='comment_edit'),
+ path(
+ 'history/ajax',
+ comment.CommentRevisionAjax.as_view(),
+ name='comment_revision_ajax',
+ ),
+ path(
+ 'edit/ajax',
+ comment.CommentEditAjax.as_view(),
+ name='comment_edit_ajax',
+ ),
+ path(
+ 'votes/ajax',
+ comment.CommentVotesAjax.as_view(),
+ name='comment_votes_ajax',
+ ),
+ path(
+ 'render', comment.CommentContent.as_view(), name='comment_content'
+ ),
+ ]
+ ),
+ ),
+ path(
+ 'contests/', contests.ContestList.as_view(), name='contest_list'
+ ), # if broken add $ to end of regex and make regex
path('contests.ics', contests.ContestICal.as_view(), name='contest_ical'),
- path('contests///', contests.ContestCalendar.as_view(), name='contest_calendar'),
- re_path(r'^contests/tag/(?P[a-z-]+)', include([
- path('', contests.ContestTagDetail.as_view(), name='contest_tag'),
- path('/ajax', contests.ContestTagDetailAjax.as_view(), name='contest_tag_ajax'),
- ])),
-
- path('contest/', include([
- path('', contests.ContestDetail.as_view(), name='contest_view'),
- path('/moss', contests.ContestMossView.as_view(), name='contest_moss'),
- path('/moss/delete', contests.ContestMossDelete.as_view(), name='contest_moss_delete'),
- path('/clone', contests.ContestClone.as_view(), name='contest_clone'),
- path('/ranking/', contests.ContestRanking.as_view(), name='contest_ranking'),
- path('/ranking/ajax', contests.contest_ranking_ajax, name='contest_ranking_ajax'),
- path('/register', contests.ContestRegister.as_view(), name='contest_register'),
- path('/join', contests.ContestJoin.as_view(), name='contest_join'),
- path('/leave', contests.ContestLeave.as_view(), name='contest_leave'),
- path('/stats', contests.ContestStats.as_view(), name='contest_stats'),
-
- path('/rank//',
- paged_list_view(ranked_submission.ContestRankedSubmission, 'contest_ranked_submissions')),
-
- path('/submissions//',
- paged_list_view(submission.UserAllContestSubmissions, 'contest_all_user_submissions')),
- path('/submissions///',
- paged_list_view(submission.UserContestSubmissions, 'contest_user_submissions')),
-
- path('/participations', contests.ContestParticipationList.as_view(), name='contest_participation_own'),
- path('/participations/',
- contests.ContestParticipationList.as_view(), name='contest_participation'),
- path('/participation/disqualify', contests.ContestParticipationDisqualify.as_view(),
- name='contest_participation_disqualify'),
-
- path('/', lambda _, contest: HttpResponsePermanentRedirect(reverse('contest_view', args=[contest]))),
- ])),
-
- path('organizations/', organization.OrganizationList.as_view(), name='organization_list'),
- path('organization/-', include([
- path('', organization.OrganizationHome.as_view(), name='organization_home'),
- path('/users', organization.OrganizationUsers.as_view(), name='organization_users'),
- path('/join', organization.JoinOrganization.as_view(), name='join_organization'),
- path('/leave', organization.LeaveOrganization.as_view(), name='leave_organization'),
- path('/edit', organization.EditOrganization.as_view(), name='edit_organization'),
- path('/kick', organization.KickUserWidgetView.as_view(), name='organization_user_kick'),
-
- path('/request', organization.RequestJoinOrganization.as_view(), name='request_organization'),
- path('/request/', organization.OrganizationRequestDetail.as_view(),
- name='request_organization_detail'),
- path('/requests/', include([
- path('pending', organization.OrganizationRequestView.as_view(), name='organization_requests_pending'),
- path('log', organization.OrganizationRequestLog.as_view(), name='organization_requests_log'),
- path('approved', organization.OrganizationRequestLog.as_view(states=('A',), tab='approved'),
- name='organization_requests_approved'),
- path('rejected', organization.OrganizationRequestLog.as_view(states=('R',), tab='rejected'),
- name='organization_requests_rejected'),
- ])),
-
- path('/class/-', include([
- path('', organization.ClassHome.as_view(), name='class_home'),
- path('/join', organization.RequestJoinClass.as_view(), name='class_join'),
- ])),
-
- path('/', lambda _, pk, slug: HttpResponsePermanentRedirect(reverse('organization_home', args=[pk, slug]))),
- ])),
-
+ path(
+ 'contests///',
+ contests.ContestCalendar.as_view(),
+ name='contest_calendar',
+ ),
+ re_path(
+ r'^contests/tag/(?P[a-z-]+)',
+ include(
+ [
+ path('', contests.ContestTagDetail.as_view(), name='contest_tag'),
+ path(
+ '/ajax',
+ contests.ContestTagDetailAjax.as_view(),
+ name='contest_tag_ajax',
+ ),
+ ]
+ ),
+ ),
+ path(
+ 'contest/',
+ include(
+ [
+ path('', contests.ContestDetail.as_view(), name='contest_view'),
+ path('/moss', contests.ContestMossView.as_view(), name='contest_moss'),
+ path(
+ '/moss/delete',
+ contests.ContestMossDelete.as_view(),
+ name='contest_moss_delete',
+ ),
+ path('/clone', contests.ContestClone.as_view(), name='contest_clone'),
+ path(
+ '/ranking/',
+ contests.ContestRanking.as_view(),
+ name='contest_ranking',
+ ),
+ path(
+ '/ranking/ajax',
+ contests.contest_ranking_ajax,
+ name='contest_ranking_ajax',
+ ),
+ path(
+ '/register',
+ contests.ContestRegister.as_view(),
+ name='contest_register',
+ ),
+ path('/join', contests.ContestJoin.as_view(), name='contest_join'),
+ path('/leave', contests.ContestLeave.as_view(), name='contest_leave'),
+ path('/stats', contests.ContestStats.as_view(), name='contest_stats'),
+ path(
+ '/rank//',
+ paged_list_view(
+ ranked_submission.ContestRankedSubmission,
+ 'contest_ranked_submissions',
+ ),
+ ),
+ path(
+ '/submissions//',
+ paged_list_view(
+ submission.UserAllContestSubmissions,
+ 'contest_all_user_submissions',
+ ),
+ ),
+ path(
+ '/submissions///',
+ paged_list_view(
+ submission.UserContestSubmissions, 'contest_user_submissions'
+ ),
+ ),
+ path(
+ '/participations',
+ contests.ContestParticipationList.as_view(),
+ name='contest_participation_own',
+ ),
+ path(
+ '/participations/',
+ contests.ContestParticipationList.as_view(),
+ name='contest_participation',
+ ),
+ path(
+ '/participation/disqualify',
+ contests.ContestParticipationDisqualify.as_view(),
+ name='contest_participation_disqualify',
+ ),
+ path(
+ '/',
+ lambda _, contest: HttpResponsePermanentRedirect(
+ reverse('contest_view', args=[contest])
+ ),
+ ),
+ ]
+ ),
+ ),
+ path(
+ 'organizations/',
+ organization.OrganizationList.as_view(),
+ name='organization_list',
+ ),
+ path(
+ 'organization/-',
+ include(
+ [
+ path(
+ '',
+ organization.OrganizationHome.as_view(),
+ name='organization_home',
+ ),
+ path(
+ '/users',
+ organization.OrganizationUsers.as_view(),
+ name='organization_users',
+ ),
+ path(
+ '/join',
+ organization.JoinOrganization.as_view(),
+ name='join_organization',
+ ),
+ path(
+ '/leave',
+ organization.LeaveOrganization.as_view(),
+ name='leave_organization',
+ ),
+ path(
+ '/edit',
+ organization.EditOrganization.as_view(),
+ name='edit_organization',
+ ),
+ path(
+ '/kick',
+ organization.KickUserWidgetView.as_view(),
+ name='organization_user_kick',
+ ),
+ path(
+ '/request',
+ organization.RequestJoinOrganization.as_view(),
+ name='request_organization',
+ ),
+ path(
+ '/request/',
+ organization.OrganizationRequestDetail.as_view(),
+ name='request_organization_detail',
+ ),
+ path(
+ '/requests/',
+ include(
+ [
+ path(
+ 'pending',
+ organization.OrganizationRequestView.as_view(),
+ name='organization_requests_pending',
+ ),
+ path(
+ 'log',
+ organization.OrganizationRequestLog.as_view(),
+ name='organization_requests_log',
+ ),
+ path(
+ 'approved',
+ organization.OrganizationRequestLog.as_view(
+ states=('A',), tab='approved'
+ ),
+ name='organization_requests_approved',
+ ),
+ path(
+ 'rejected',
+ organization.OrganizationRequestLog.as_view(
+ states=('R',), tab='rejected'
+ ),
+ name='organization_requests_rejected',
+ ),
+ ]
+ ),
+ ),
+ path(
+ '/class/-',
+ include(
+ [
+ path(
+ '', organization.ClassHome.as_view(), name='class_home'
+ ),
+ path(
+ '/join',
+ organization.RequestJoinClass.as_view(),
+ name='class_join',
+ ),
+ ]
+ ),
+ ),
+ path(
+ '/',
+ lambda _, pk, slug: HttpResponsePermanentRedirect(
+ reverse('organization_home', args=[pk, slug])
+ ),
+ ),
+ ]
+ ),
+ ),
path('runtimes/', language.LanguageList.as_view(), name='runtime_list'),
path('runtimes/matrix/', status.version_matrix, name='version_matrix'),
path('status/', status.status_all, name='status_all'),
-
- path('api/v2/', include([
- path('contests', api.api_v2.APIContestList.as_view()),
- path('contest/', api.api_v2.APIContestDetail.as_view()),
- path('problems', api.api_v2.APIProblemList.as_view()),
- path('problem/', api.api_v2.APIProblemDetail.as_view()),
- path('users', api.api_v2.APIUserList.as_view()),
- path('user/', api.api_v2.APIUserDetail.as_view()),
- path('submissions', api.api_v2.APISubmissionList.as_view()),
- path('submission/', api.api_v2.APISubmissionDetail.as_view()),
- path('organizations', api.api_v2.APIOrganizationList.as_view()),
- path('participations', api.api_v2.APIContestParticipationList.as_view()),
- path('languages', api.api_v2.APILanguageList.as_view()),
- path('judges', api.api_v2.APIJudgeList.as_view()),
- ])),
-
+ path(
+ 'api/v2/',
+ include(
+ [
+ path('contests', api.api_v2.APIContestList.as_view()),
+ path('contest/', api.api_v2.APIContestDetail.as_view()),
+ path('problems', api.api_v2.APIProblemList.as_view()),
+ path('problem/', api.api_v2.APIProblemDetail.as_view()),
+ path('users', api.api_v2.APIUserList.as_view()),
+ path('user/', api.api_v2.APIUserDetail.as_view()),
+ path('submissions', api.api_v2.APISubmissionList.as_view()),
+ path(
+ 'submission/',
+ api.api_v2.APISubmissionDetail.as_view(),
+ ),
+ path('organizations', api.api_v2.APIOrganizationList.as_view()),
+ path(
+ 'participations', api.api_v2.APIContestParticipationList.as_view()
+ ),
+ path('languages', api.api_v2.APILanguageList.as_view()),
+ path('judges', api.api_v2.APIJudgeList.as_view()),
+ ]
+ ),
+ ),
path('blog/', paged_list_view(blog.PostList, 'blog_post_list')),
path('post/-', blog.PostView.as_view(), name='blog_post'),
-
path('license/', license.LicenseDetail.as_view(), name='license'),
-
- path('mailgun/mail_activate/', mailgun.MailgunActivationView.as_view(), name='mailgun_activate'),
-
- path('widgets/', include([
- path('rejudge', widgets.rejudge_submission, name='submission_rejudge'),
- path('single_submission', submission.single_submission, name='submission_single_query'),
- path('submission_testcases', submission.SubmissionTestCaseQuery.as_view(), name='submission_testcases_query'),
- path('status-table', status.status_table, name='status_table'),
-
- path('template', problem.LanguageTemplateAjax.as_view(), name='language_template_ajax'),
-
- path('select2/', include([
- path('user_search', UserSearchSelect2View.as_view(), name='user_search_select2_ajax'),
- path('contest_users/', ContestUserSearchSelect2View.as_view(),
- name='contest_user_search_select2_ajax'),
- path('ticket_user', TicketUserSelect2View.as_view(), name='ticket_user_select2_ajax'),
- path('ticket_assignee', AssigneeSelect2View.as_view(), name='ticket_assignee_select2_ajax'),
- ])),
-
- path('preview/', include([
- path('default', preview.DefaultMarkdownPreviewView.as_view(), name='default_preview'),
- path('problem', preview.ProblemMarkdownPreviewView.as_view(), name='problem_preview'),
- path('blog', preview.BlogMarkdownPreviewView.as_view(), name='blog_preview'),
- path('contest', preview.ContestMarkdownPreviewView.as_view(), name='contest_preview'),
- path('comment', preview.CommentMarkdownPreviewView.as_view(), name='comment_preview'),
- path('flatpage', preview.FlatPageMarkdownPreviewView.as_view(), name='flatpage_preview'),
- path('profile', preview.ProfileMarkdownPreviewView.as_view(), name='profile_preview'),
- path('organization', preview.OrganizationMarkdownPreviewView.as_view(), name='organization_preview'),
- path('solution', preview.SolutionMarkdownPreviewView.as_view(), name='solution_preview'),
- path('license', preview.LicenseMarkdownPreviewView.as_view(), name='license_preview'),
- path('ticket', preview.TicketMarkdownPreviewView.as_view(), name='ticket_preview'),
- ])),
-
- path('martor/', include([
- path('upload-image', martor_image_uploader, name='martor_image_uploader'),
- path('search-user', markdown_search_user, name='martor_search_user'),
- ])),
- ])),
-
- path('feed/', include([
- path('problems/rss/', ProblemFeed(), name='problem_rss'),
- path('problems/atom/', AtomProblemFeed(), name='problem_atom'),
- path('comment/rss/', CommentFeed(), name='comment_rss'),
- path('comment/atom/', AtomCommentFeed(), name='comment_atom'),
- path('blog/rss/', BlogFeed(), name='blog_rss'),
- path('blog/atom/', AtomBlogFeed(), name='blog_atom'),
- ])),
-
- path('stats/', include([
- path('language/', include([
- path('', stats.language, name='language_stats'),
- path('data/all/', stats.language_data, name='language_stats_data_all'),
- path('data/ac/', stats.ac_language_data, name='language_stats_data_ac'),
- path('data/status/', stats.status_data, name='stats_data_status'),
- path('data/ac_rate/', stats.ac_rate, name='language_stats_data_ac_rate'),
- ])),
- ])),
-
- path('tickets/', include([
- path('', ticket.TicketList.as_view(), name='ticket_list'),
- path('ajax', ticket.TicketListDataAjax.as_view(), name='ticket_ajax'),
- ])),
-
- path('ticket/', include([
- path('', ticket.TicketView.as_view(), name='ticket'),
- path('/ajax', ticket.TicketMessageDataAjax.as_view(), name='ticket_message_ajax'),
- path('/open', ticket.TicketStatusChangeView.as_view(open=True), name='ticket_open'),
- path('/close', ticket.TicketStatusChangeView.as_view(open=False), name='ticket_close'),
- path('/notes', ticket.TicketNotesEditView.as_view(), name='ticket_notes'),
- ])),
-
+ path(
+ 'mailgun/mail_activate/',
+ mailgun.MailgunActivationView.as_view(),
+ name='mailgun_activate',
+ ),
+ path(
+ 'widgets/',
+ include(
+ [
+ path('rejudge', widgets.rejudge_submission, name='submission_rejudge'),
+ path(
+ 'single_submission',
+ submission.single_submission,
+ name='submission_single_query',
+ ),
+ path(
+ 'submission_testcases',
+ submission.SubmissionTestCaseQuery.as_view(),
+ name='submission_testcases_query',
+ ),
+ path('status-table', status.status_table, name='status_table'),
+ path(
+ 'template',
+ problem.LanguageTemplateAjax.as_view(),
+ name='language_template_ajax',
+ ),
+ path(
+ 'select2/',
+ include(
+ [
+ path(
+ 'user_search',
+ UserSearchSelect2View.as_view(),
+ name='user_search_select2_ajax',
+ ),
+ path(
+ 'contest_users/',
+ ContestUserSearchSelect2View.as_view(),
+ name='contest_user_search_select2_ajax',
+ ),
+ path(
+ 'ticket_user',
+ TicketUserSelect2View.as_view(),
+ name='ticket_user_select2_ajax',
+ ),
+ path(
+ 'ticket_assignee',
+ AssigneeSelect2View.as_view(),
+ name='ticket_assignee_select2_ajax',
+ ),
+ ]
+ ),
+ ),
+ path(
+ 'preview/',
+ include(
+ [
+ path(
+ 'default',
+ preview.DefaultMarkdownPreviewView.as_view(),
+ name='default_preview',
+ ),
+ path(
+ 'problem',
+ preview.ProblemMarkdownPreviewView.as_view(),
+ name='problem_preview',
+ ),
+ path(
+ 'blog',
+ preview.BlogMarkdownPreviewView.as_view(),
+ name='blog_preview',
+ ),
+ path(
+ 'contest',
+ preview.ContestMarkdownPreviewView.as_view(),
+ name='contest_preview',
+ ),
+ path(
+ 'comment',
+ preview.CommentMarkdownPreviewView.as_view(),
+ name='comment_preview',
+ ),
+ path(
+ 'flatpage',
+ preview.FlatPageMarkdownPreviewView.as_view(),
+ name='flatpage_preview',
+ ),
+ path(
+ 'profile',
+ preview.ProfileMarkdownPreviewView.as_view(),
+ name='profile_preview',
+ ),
+ path(
+ 'organization',
+ preview.OrganizationMarkdownPreviewView.as_view(),
+ name='organization_preview',
+ ),
+ path(
+ 'solution',
+ preview.SolutionMarkdownPreviewView.as_view(),
+ name='solution_preview',
+ ),
+ path(
+ 'license',
+ preview.LicenseMarkdownPreviewView.as_view(),
+ name='license_preview',
+ ),
+ path(
+ 'ticket',
+ preview.TicketMarkdownPreviewView.as_view(),
+ name='ticket_preview',
+ ),
+ ]
+ ),
+ ),
+ path(
+ 'martor/',
+ include(
+ [
+ path(
+ 'upload-image',
+ martor_image_uploader,
+ name='martor_image_uploader',
+ ),
+ path(
+ 'search-user',
+ markdown_search_user,
+ name='martor_search_user',
+ ),
+ ]
+ ),
+ ),
+ ]
+ ),
+ ),
+ path(
+ 'feed/',
+ include(
+ [
+ path('problems/rss/', ProblemFeed(), name='problem_rss'),
+ path('problems/atom/', AtomProblemFeed(), name='problem_atom'),
+ path('comment/rss/', CommentFeed(), name='comment_rss'),
+ path('comment/atom/', AtomCommentFeed(), name='comment_atom'),
+ path('blog/rss/', BlogFeed(), name='blog_rss'),
+ path('blog/atom/', AtomBlogFeed(), name='blog_atom'),
+ ]
+ ),
+ ),
+ path(
+ 'stats/',
+ include(
+ [
+ path(
+ 'language/',
+ include(
+ [
+ path('', stats.language, name='language_stats'),
+ path(
+ 'data/all/',
+ stats.language_data,
+ name='language_stats_data_all',
+ ),
+ path(
+ 'data/ac/',
+ stats.ac_language_data,
+ name='language_stats_data_ac',
+ ),
+ path(
+ 'data/status/',
+ stats.status_data,
+ name='stats_data_status',
+ ),
+ path(
+ 'data/ac_rate/',
+ stats.ac_rate,
+ name='language_stats_data_ac_rate',
+ ),
+ ]
+ ),
+ ),
+ ]
+ ),
+ ),
+ path(
+ 'tickets/',
+ include(
+ [
+ path('', ticket.TicketList.as_view(), name='ticket_list'),
+ path('ajax', ticket.TicketListDataAjax.as_view(), name='ticket_ajax'),
+ ]
+ ),
+ ),
+ path(
+ 'ticket/',
+ include(
+ [
+ path('', ticket.TicketView.as_view(), name='ticket'),
+ path(
+ '/ajax',
+ ticket.TicketMessageDataAjax.as_view(),
+ name='ticket_message_ajax',
+ ),
+ path(
+ '/open',
+ ticket.TicketStatusChangeView.as_view(open=True),
+ name='ticket_open',
+ ),
+ path(
+ '/close',
+ ticket.TicketStatusChangeView.as_view(open=False),
+ name='ticket_close',
+ ),
+ path(
+ '/notes', ticket.TicketNotesEditView.as_view(), name='ticket_notes'
+ ),
+ ]
+ ),
+ ),
path('sitemap.xml', sitemap, {'sitemaps': sitemaps}),
-
- path('judge-select2/', include([
- path('profile/', UserSelect2View.as_view(), name='profile_select2'),
- path('organization/', OrganizationSelect2View.as_view(), name='organization_select2'),
- path('class/', ClassSelect2View.as_view(), name='class_select2'),
- path('problem/', ProblemSelect2View.as_view(), name='problem_select2'),
- path('contest/', ContestSelect2View.as_view(), name='contest_select2'),
- path('comment/', CommentSelect2View.as_view(), name='comment_select2'),
- ])),
-
- path('tasks/', include([
- path('status/', tasks.task_status, name='task_status'),
- path('ajax_status', tasks.task_status_ajax, name='task_status_ajax'),
- path('success', tasks.demo_success),
- path('failure', tasks.demo_failure),
- path('progress', tasks.demo_progress),
- ])),
+ path(
+ 'judge-select2/',
+ include(
+ [
+ path('profile/', UserSelect2View.as_view(), name='profile_select2'),
+ path(
+ 'organization/',
+ OrganizationSelect2View.as_view(),
+ name='organization_select2',
+ ),
+ path('class/', ClassSelect2View.as_view(), name='class_select2'),
+ path('problem/', ProblemSelect2View.as_view(), name='problem_select2'),
+ path('contest/', ContestSelect2View.as_view(), name='contest_select2'),
+ path('comment/', CommentSelect2View.as_view(), name='comment_select2'),
+ ]
+ ),
+ ),
+ path(
+ 'tasks/',
+ include(
+ [
+ path('status/', tasks.task_status, name='task_status'),
+ path('ajax_status', tasks.task_status_ajax, name='task_status_ajax'),
+ path('success', tasks.demo_success),
+ path('failure', tasks.demo_failure),
+ path('progress', tasks.demo_progress),
+ ]
+ ),
+ ),
]
-favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png',
- 'apple-touch-icon-57x57.png', 'apple-touch-icon-72x72.png', 'apple-touch-icon.png', 'mstile-70x70.png',
- 'android-chrome-36x36.png', 'apple-touch-icon-precomposed.png', 'apple-touch-icon-76x76.png',
- 'apple-touch-icon-60x60.png', 'android-chrome-96x96.png', 'mstile-144x144.png', 'mstile-150x150.png',
- 'safari-pinned-tab.svg', 'android-chrome-144x144.png', 'apple-touch-icon-152x152.png',
- 'favicon-96x96.png',
- 'favicon-32x32.png', 'favicon-16x16.png', 'android-chrome-192x192.png', 'android-chrome-48x48.png',
- 'mstile-310x150.png', 'apple-touch-icon-144x144.png', 'browserconfig.xml', 'manifest.json',
- 'apple-touch-icon-120x120.png', 'mstile-310x310.png']
+favicon_paths = [
+ 'apple-touch-icon-180x180.png',
+ 'apple-touch-icon-114x114.png',
+ 'android-chrome-72x72.png',
+ 'apple-touch-icon-57x57.png',
+ 'apple-touch-icon-72x72.png',
+ 'apple-touch-icon.png',
+ 'mstile-70x70.png',
+ 'android-chrome-36x36.png',
+ 'apple-touch-icon-precomposed.png',
+ 'apple-touch-icon-76x76.png',
+ 'apple-touch-icon-60x60.png',
+ 'android-chrome-96x96.png',
+ 'mstile-144x144.png',
+ 'mstile-150x150.png',
+ 'safari-pinned-tab.svg',
+ 'android-chrome-144x144.png',
+ 'apple-touch-icon-152x152.png',
+ 'favicon-96x96.png',
+ 'favicon-32x32.png',
+ 'favicon-16x16.png',
+ 'android-chrome-192x192.png',
+ 'android-chrome-48x48.png',
+ 'mstile-310x150.png',
+ 'apple-touch-icon-144x144.png',
+ 'browserconfig.xml',
+ 'manifest.json',
+ 'apple-touch-icon-120x120.png',
+ 'mstile-310x310.png',
+]
static_lazy = lazy(static, str)
for favicon in favicon_paths:
- urlpatterns.append(path(favicon, RedirectView.as_view(
- url=static_lazy('icons/' + favicon),
- )))
+ urlpatterns.append(
+ path(
+ favicon,
+ RedirectView.as_view(
+ url=static_lazy('icons/' + favicon),
+ ),
+ )
+ )
handler404 = 'judge.views.error.error404'
handler403 = 'judge.views.error.error403'
diff --git a/dmoj/wsgi.py b/dmoj/wsgi.py
index 6bec753460..c09eda8295 100644
--- a/dmoj/wsgi.py
+++ b/dmoj/wsgi.py
@@ -1,4 +1,5 @@
import os
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dmoj.settings')
try:
@@ -8,5 +9,8 @@
pymysql.install_as_MySQLdb()
-from django.core.wsgi import get_wsgi_application # noqa: E402, django must be imported here
+from django.core.wsgi import (
+ get_wsgi_application,
+) # noqa: E402, django must be imported here
+
application = get_wsgi_application()
diff --git a/dmoj/wsgi_async.py b/dmoj/wsgi_async.py
index ec114d1fd8..cc74147934 100644
--- a/dmoj/wsgi_async.py
+++ b/dmoj/wsgi_async.py
@@ -8,5 +8,8 @@
# noinspection PyUnresolvedReferences
import dmoj_install_pymysql # noqa: E402, F401, I100, I202, imported for side effect
-from django.core.wsgi import get_wsgi_application # noqa: E402, I100, I202, django must be imported here
+from django.core.wsgi import (
+ get_wsgi_application,
+) # noqa: E402, I100, I202, django must be imported here
+
application = get_wsgi_application()
diff --git a/dmoj_bridge_async.py b/dmoj_bridge_async.py
index 376f8cf8d0..f224d0d67b 100644
--- a/dmoj_bridge_async.py
+++ b/dmoj_bridge_async.py
@@ -9,9 +9,12 @@
import dmoj_install_pymysql # noqa: E402, F401, I100, I202, imported for side effect
import django # noqa: E402, F401, I100, I202, django must be imported here
+
django.setup()
-from judge.bridge.daemon import judge_daemon # noqa: E402, I100, I202, django code must be imported here
+from judge.bridge.daemon import (
+ judge_daemon,
+) # noqa: E402, I100, I202, django code must be imported here
if __name__ == '__main__':
judge_daemon()
diff --git a/judge/admin/__init__.py b/judge/admin/__init__.py
index aa3d475ccc..a27ce6517c 100644
--- a/judge/admin/__init__.py
+++ b/judge/admin/__init__.py
@@ -4,18 +4,52 @@
from django.contrib.flatpages.models import FlatPage
from judge.admin.comments import CommentAdmin
-from judge.admin.contest import ContestAdmin, ContestParticipationAdmin, ContestRegistrationAdmin, ContestTagAdmin
-from judge.admin.interface import BlogPostAdmin, FlatPageAdmin, LicenseAdmin, LogEntryAdmin, NavigationBarAdmin
-from judge.admin.organization import ClassAdmin, OrganizationAdmin, OrganizationRequestAdmin
+from judge.admin.contest import (
+ ContestAdmin,
+ ContestParticipationAdmin,
+ ContestRegistrationAdmin,
+ ContestTagAdmin,
+)
+from judge.admin.interface import (
+ BlogPostAdmin,
+ FlatPageAdmin,
+ LicenseAdmin,
+ LogEntryAdmin,
+ NavigationBarAdmin,
+)
+from judge.admin.organization import (
+ ClassAdmin,
+ OrganizationAdmin,
+ OrganizationRequestAdmin,
+)
from judge.admin.problem import ProblemAdmin, ProblemPointsVoteAdmin
from judge.admin.profile import ProfileAdmin, UserAdmin
from judge.admin.runtime import JudgeAdmin, LanguageAdmin
from judge.admin.submission import SubmissionAdmin
from judge.admin.taxon import ProblemGroupAdmin, ProblemTypeAdmin
from judge.admin.ticket import TicketAdmin
-from judge.models import BlogPost, Comment, CommentLock, Contest, ContestParticipation, \
- ContestRegistration, ContestTag, Judge, Language, License, MiscConfig, NavigationBar, \
- Organization, OrganizationRequest, Problem, ProblemGroup, ProblemType, Profile, Submission, Ticket
+from judge.models import (
+ BlogPost,
+ Comment,
+ CommentLock,
+ Contest,
+ ContestParticipation,
+ ContestRegistration,
+ ContestTag,
+ Judge,
+ Language,
+ License,
+ MiscConfig,
+ NavigationBar,
+ Organization,
+ OrganizationRequest,
+ Problem,
+ ProblemGroup,
+ ProblemType,
+ Profile,
+ Submission,
+ Ticket,
+)
admin.site.register(BlogPost, BlogPostAdmin)
admin.site.register(Comment, CommentAdmin)
diff --git a/judge/admin/comments.py b/judge/admin/comments.py
index a0e28b5633..9b9a259f07 100644
--- a/judge/admin/comments.py
+++ b/judge/admin/comments.py
@@ -15,7 +15,9 @@ class Meta:
widgets = {
'author': AdminHeavySelect2Widget(data_view='profile_select2'),
'parent': AdminHeavySelect2Widget(data_view='comment_select2'),
- 'body': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('comment_preview')}),
+ 'body': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('comment_preview')}
+ ),
}
@@ -40,16 +42,28 @@ def get_queryset(self, request):
@admin.display(description=_('Hide comments'))
def hide_comment(self, request, queryset):
count = queryset.update(hidden=True)
- self.message_user(request, ngettext('%d comment successfully hidden.',
- '%d comments successfully hidden.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d comment successfully hidden.',
+ '%d comments successfully hidden.',
+ count,
+ )
+ % count,
+ )
@admin.display(description=_('Unhide comments'))
def unhide_comment(self, request, queryset):
count = queryset.update(hidden=False)
- self.message_user(request, ngettext('%d comment successfully unhidden.',
- '%d comments successfully unhidden.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d comment successfully unhidden.',
+ '%d comments successfully unhidden.',
+ count,
+ )
+ % count,
+ )
@admin.display(description=_('associated page'), ordering='page')
def linked_page(self, obj):
diff --git a/judge/admin/contest.py b/judge/admin/contest.py
index 59c25d103a..d172b618d6 100644
--- a/judge/admin/contest.py
+++ b/judge/admin/contest.py
@@ -13,11 +13,24 @@
from reversion.admin import VersionAdmin
from django_ace import AceWidget
-from judge.models import Class, Contest, ContestProblem, ContestSubmission, Profile, Rating, Submission
+from judge.models import (
+ Class,
+ Contest,
+ ContestProblem,
+ ContestSubmission,
+ Profile,
+ Rating,
+ Submission,
+)
from judge.ratings import rate_contest
from judge.utils.views import NoBatchDeleteMixin
-from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminMartorWidget, \
- AdminSelect2MultipleWidget, AdminSelect2Widget
+from judge.widgets import (
+ AdminHeavySelect2MultipleWidget,
+ AdminHeavySelect2Widget,
+ AdminMartorWidget,
+ AdminSelect2MultipleWidget,
+ AdminSelect2Widget,
+)
class AdminHeavySelect2Widget(AdminHeavySelect2Widget):
@@ -31,7 +44,8 @@ class ContestTagForm(ModelForm):
label=_('Included contests'),
queryset=Contest.objects.all(),
required=False,
- widget=AdminHeavySelect2MultipleWidget(data_view='contest_select2'))
+ widget=AdminHeavySelect2MultipleWidget(data_view='contest_select2'),
+ )
class ContestTagAdmin(admin.ModelAdmin):
@@ -64,8 +78,16 @@ class ContestProblemInline(SortableInlineAdminMixin, admin.TabularInline):
model = ContestProblem
verbose_name = _('Problem')
verbose_name_plural = _('Problems')
- fields = ('problem', 'points', 'partial', 'is_pretested', 'max_submissions', 'output_prefix_override', 'order',
- 'rejudge_column')
+ fields = (
+ 'problem',
+ 'points',
+ 'partial',
+ 'is_pretested',
+ 'max_submissions',
+ 'output_prefix_override',
+ 'order',
+ 'rejudge_column',
+ )
readonly_fields = ('rejudge_column',)
form = ContestProblemInlineForm
@@ -73,8 +95,11 @@ class ContestProblemInline(SortableInlineAdminMixin, admin.TabularInline):
def rejudge_column(self, obj):
if obj.id is None:
return ''
- return format_html('{1} ',
- reverse('admin:judge_contest_rejudge', args=(obj.contest.id, obj.id)), _('Rejudge'))
+ return format_html(
+ '{1} ',
+ reverse('admin:judge_contest_rejudge', args=(obj.contest.id, obj.id)),
+ _('Rejudge'),
+ )
class ContestForm(ModelForm):
@@ -82,8 +107,9 @@ def __init__(self, *args, **kwargs):
super(ContestForm, self).__init__(*args, **kwargs)
if 'rate_exclude' in self.fields:
if self.instance and self.instance.id:
- self.fields['rate_exclude'].queryset = \
- Profile.objects.filter(contest_history__contest=self.instance).distinct()
+ self.fields['rate_exclude'].queryset = Profile.objects.filter(
+ contest_history__contest=self.instance
+ ).distinct()
else:
self.fields['rate_exclude'].queryset = Profile.objects.none()
self.fields['banned_users'].widget.can_add_related = False
@@ -91,7 +117,9 @@ def __init__(self, *args, **kwargs):
def clean(self):
cleaned_data = super(ContestForm, self).clean()
- cleaned_data['banned_users'].filter(current_contest__contest=self.instance).update(current_contest=None)
+ cleaned_data['banned_users'].filter(
+ current_contest__contest=self.instance
+ ).update(current_contest=None)
class Meta:
widgets = {
@@ -99,39 +127,121 @@ class Meta:
'curators': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
'testers': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
'spectators': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
- 'private_contestants': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
- attrs={'style': 'width: 100%'}),
- 'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2'),
+ 'private_contestants': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'organizations': AdminHeavySelect2MultipleWidget(
+ data_view='organization_select2'
+ ),
'classes': AdminHeavySelect2MultipleWidget(data_view='class_select2'),
- 'join_organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2'),
+ 'join_organizations': AdminHeavySelect2MultipleWidget(
+ data_view='organization_select2'
+ ),
'tags': AdminSelect2MultipleWidget,
- 'banned_users': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
- attrs={'style': 'width: 100%'}),
- 'view_contest_scoreboard': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
- attrs={'style': 'width: 100%'}),
- 'view_contest_submissions': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
- attrs={'style': 'width: 100%'}),
- 'description': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('contest_preview')}),
+ 'banned_users': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'view_contest_scoreboard': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'view_contest_submissions': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'description': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('contest_preview')}
+ ),
}
class ContestAdmin(NoBatchDeleteMixin, VersionAdmin):
fieldsets = (
- (None, {'fields': ('key', 'name', 'authors', 'curators', 'testers', 'tester_see_submissions',
- 'tester_see_scoreboard', 'spectators')}),
- (_('Settings'), {'fields': ('is_visible', 'use_clarifications', 'hide_problem_tags', 'hide_problem_authors',
- 'show_short_display', 'run_pretests_only', 'locked_after', 'scoreboard_visibility',
- 'points_precision')}),
+ (
+ None,
+ {
+ 'fields': (
+ 'key',
+ 'name',
+ 'authors',
+ 'curators',
+ 'testers',
+ 'tester_see_submissions',
+ 'tester_see_scoreboard',
+ 'spectators',
+ )
+ },
+ ),
+ (
+ _('Settings'),
+ {
+ 'fields': (
+ 'is_visible',
+ 'use_clarifications',
+ 'hide_problem_tags',
+ 'hide_problem_authors',
+ 'show_short_display',
+ 'run_pretests_only',
+ 'locked_after',
+ 'scoreboard_visibility',
+ 'points_precision',
+ )
+ },
+ ),
(_('Scheduling'), {'fields': ('start_time', 'end_time', 'time_limit')}),
- (_('Details'), {'fields': ('description', 'og_image', 'logo_override_image', 'tags', 'summary')}),
- (_('Format'), {'fields': ('format_name', 'format_config', 'problem_label_script')}),
- (_('Rating'), {'fields': ('is_rated', 'rate_all', 'rating_floor', 'rating_ceiling', 'rate_exclude')}),
- (_('Access'), {'fields': ('access_code', 'private_contestants', 'organizations', 'classes',
- 'join_organizations', 'view_contest_scoreboard', 'view_contest_submissions')}),
+ (
+ _('Details'),
+ {
+ 'fields': (
+ 'description',
+ 'og_image',
+ 'logo_override_image',
+ 'tags',
+ 'summary',
+ )
+ },
+ ),
+ (
+ _('Format'),
+ {'fields': ('format_name', 'format_config', 'problem_label_script')},
+ ),
+ (
+ _('Rating'),
+ {
+ 'fields': (
+ 'is_rated',
+ 'rate_all',
+ 'rating_floor',
+ 'rating_ceiling',
+ 'rate_exclude',
+ )
+ },
+ ),
+ (
+ _('Access'),
+ {
+ 'fields': (
+ 'access_code',
+ 'private_contestants',
+ 'organizations',
+ 'classes',
+ 'join_organizations',
+ 'view_contest_scoreboard',
+ 'view_contest_submissions',
+ )
+ },
+ ),
(_('Justice'), {'fields': ('banned_users',)}),
)
- list_display = ('key', 'name', 'is_visible', 'is_rated', 'locked_after', 'start_time', 'end_time', 'time_limit',
- 'user_count')
+ list_display = (
+ 'key',
+ 'name',
+ 'is_visible',
+ 'is_rated',
+ 'locked_after',
+ 'start_time',
+ 'end_time',
+ 'time_limit',
+ 'user_count',
+ )
search_fields = ('key', 'name')
inlines = [ContestProblemInline]
actions_on_top = True
@@ -144,8 +254,9 @@ class ContestAdmin(NoBatchDeleteMixin, VersionAdmin):
def get_actions(self, request):
actions = super(ContestAdmin, self).get_actions(request)
- if request.user.has_perm('judge.change_contest_visibility') or \
- request.user.has_perm('judge.create_private_contest'):
+ if request.user.has_perm(
+ 'judge.change_contest_visibility'
+ ) or request.user.has_perm('judge.create_private_contest'):
for action in ('make_visible', 'make_hidden'):
actions[action] = self.get_action(action)
@@ -160,7 +271,9 @@ def get_queryset(self, request):
if request.user.has_perm('judge.edit_all_contest'):
return queryset
else:
- return queryset.filter(Q(authors=request.profile) | Q(curators=request.profile)).distinct()
+ return queryset.filter(
+ Q(authors=request.profile) | Q(curators=request.profile)
+ ).distinct()
def get_readonly_fields(self, request, obj=None):
readonly = []
@@ -184,12 +297,18 @@ def save_model(self, request, obj, form, change):
if 'private_contestants' in form.changed_data:
obj.is_private = bool(form.cleaned_data['private_contestants'])
if 'organizations' in form.changed_data or 'classes' in form.changed_data:
- obj.is_organization_private = bool(form.cleaned_data['organizations'] or form.cleaned_data['classes'])
+ obj.is_organization_private = bool(
+ form.cleaned_data['organizations'] or form.cleaned_data['classes']
+ )
if 'join_organizations' in form.cleaned_data:
- obj.limit_join_organizations = bool(form.cleaned_data['join_organizations'])
+ obj.limit_join_organizations = bool(
+ form.cleaned_data['join_organizations']
+ )
# `is_visible` will not appear in `cleaned_data` if user cannot edit it
- if form.cleaned_data.get('is_visible') and not request.user.has_perm('judge.change_contest_visibility'):
+ if form.cleaned_data.get('is_visible') and not request.user.has_perm(
+ 'judge.change_contest_visibility'
+ ):
if not obj.is_private and not obj.is_organization_private:
raise PermissionDenied
if not request.user.has_perm('judge.create_private_contest'):
@@ -198,7 +317,9 @@ def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
# We need this flag because `save_related` deals with the inlines, but does not know if we have already rescored
self._rescored = False
- if form.changed_data and any(f in form.changed_data for f in ('format_config', 'format_name')):
+ if form.changed_data and any(
+ f in form.changed_data for f in ('format_config', 'format_name')
+ ):
self._rescore(obj.key)
self._rescored = True
@@ -220,67 +341,111 @@ def has_change_permission(self, request, obj=None):
def _rescore(self, contest_key):
from judge.tasks import rescore_contest
+
transaction.on_commit(rescore_contest.s(contest_key).delay)
@admin.display(description=_('Mark contests as visible'))
def make_visible(self, request, queryset):
if not request.user.has_perm('judge.change_contest_visibility'):
- queryset = queryset.filter(Q(is_private=True) | Q(is_organization_private=True))
+ queryset = queryset.filter(
+ Q(is_private=True) | Q(is_organization_private=True)
+ )
count = queryset.update(is_visible=True)
- self.message_user(request, ngettext('%d contest successfully marked as visible.',
- '%d contests successfully marked as visible.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d contest successfully marked as visible.',
+ '%d contests successfully marked as visible.',
+ count,
+ )
+ % count,
+ )
@admin.display(description=_('Mark contests as hidden'))
def make_hidden(self, request, queryset):
if not request.user.has_perm('judge.change_contest_visibility'):
- queryset = queryset.filter(Q(is_private=True) | Q(is_organization_private=True))
+ queryset = queryset.filter(
+ Q(is_private=True) | Q(is_organization_private=True)
+ )
count = queryset.update(is_visible=True)
- self.message_user(request, ngettext('%d contest successfully marked as hidden.',
- '%d contests successfully marked as hidden.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d contest successfully marked as hidden.',
+ '%d contests successfully marked as hidden.',
+ count,
+ )
+ % count,
+ )
@admin.display(description=_('Lock contest submissions'))
def set_locked(self, request, queryset):
for row in queryset:
self.set_locked_after(row, timezone.now())
count = queryset.count()
- self.message_user(request, ngettext('%d contest successfully locked.',
- '%d contests successfully locked.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d contest successfully locked.',
+ '%d contests successfully locked.',
+ count,
+ )
+ % count,
+ )
@admin.display(description=_('Unlock contest submissions'))
def set_unlocked(self, request, queryset):
for row in queryset:
self.set_locked_after(row, None)
count = queryset.count()
- self.message_user(request, ngettext('%d contest successfully unlocked.',
- '%d contests successfully unlocked.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d contest successfully unlocked.',
+ '%d contests successfully unlocked.',
+ count,
+ )
+ % count,
+ )
def set_locked_after(self, contest, locked_after):
with transaction.atomic():
contest.locked_after = locked_after
contest.save()
- Submission.objects.filter(contest_object=contest,
- contest__participation__virtual=0).update(locked_after=locked_after)
+ Submission.objects.filter(
+ contest_object=contest, contest__participation__virtual=0
+ ).update(locked_after=locked_after)
def get_urls(self):
return [
path('rate/all/', self.rate_all_view, name='judge_contest_rate_all'),
path('/rate/', self.rate_view, name='judge_contest_rate'),
- path('/judge//', self.rejudge_view, name='judge_contest_rejudge'),
+ path(
+ '/judge//',
+ self.rejudge_view,
+ name='judge_contest_rejudge',
+ ),
] + super(ContestAdmin, self).get_urls()
def rejudge_view(self, request, contest_id, problem_id):
- queryset = ContestSubmission.objects.filter(problem_id=problem_id).select_related('submission')
+ queryset = ContestSubmission.objects.filter(
+ problem_id=problem_id
+ ).select_related('submission')
for model in queryset:
model.submission.judge(rejudge=True, rejudge_user=request.user)
- self.message_user(request, ngettext('%d submission was successfully scheduled for rejudging.',
- '%d submissions were successfully scheduled for rejudging.',
- len(queryset)) % len(queryset))
- return HttpResponseRedirect(reverse('admin:judge_contest_change', args=(contest_id,)))
+ self.message_user(
+ request,
+ ngettext(
+ '%d submission was successfully scheduled for rejudging.',
+ '%d submissions were successfully scheduled for rejudging.',
+ len(queryset),
+ )
+ % len(queryset),
+ )
+ return HttpResponseRedirect(
+ reverse('admin:judge_contest_change', args=(contest_id,))
+ )
def rate_all_view(self, request):
if not request.user.has_perm('judge.contest_rating'):
@@ -289,7 +454,9 @@ def rate_all_view(self, request):
with connection.cursor() as cursor:
cursor.execute('TRUNCATE TABLE `%s`' % Rating._meta.db_table)
Profile.objects.update(rating=None)
- for contest in Contest.objects.filter(is_rated=True, end_time__lte=timezone.now()).order_by('end_time'):
+ for contest in Contest.objects.filter(
+ is_rated=True, end_time__lte=timezone.now()
+ ).order_by('end_time'):
rate_contest(contest)
return HttpResponseRedirect(reverse('admin:judge_contest_changelist'))
@@ -301,7 +468,9 @@ def rate_view(self, request, id):
raise Http404()
with transaction.atomic():
contest.rate()
- return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse('admin:judge_contest_changelist')))
+ return HttpResponseRedirect(
+ request.META.get('HTTP_REFERER', reverse('admin:judge_contest_changelist'))
+ )
def get_form(self, request, obj=None, **kwargs):
form = super(ContestAdmin, self).get_form(request, obj, **kwargs)
@@ -309,14 +478,15 @@ def get_form(self, request, obj=None, **kwargs):
# form.base_fields['problem_label_script'] does not exist when the user has only view permission
# on the model.
form.base_fields['problem_label_script'].widget = AceWidget(
- mode='lua', theme=request.profile.resolved_ace_theme,
+ mode='lua',
+ theme=request.profile.resolved_ace_theme,
)
perms = ('edit_own_contest', 'edit_all_contest')
form.base_fields['curators'].queryset = Profile.objects.filter(
- Q(user__is_superuser=True) |
- Q(user__groups__permissions__codename__in=perms) |
- Q(user__user_permissions__codename__in=perms),
+ Q(user__is_superuser=True)
+ | Q(user__groups__permissions__codename__in=perms)
+ | Q(user__user_permissions__codename__in=perms),
).distinct()
form.base_fields['classes'].queryset = Class.get_visible_classes(request.user)
return form
@@ -332,7 +502,15 @@ class Meta:
class ContestParticipationAdmin(admin.ModelAdmin):
fields = ('contest', 'user', 'real_start', 'virtual', 'is_disqualified')
- list_display = ('contest', 'username', 'show_virtual', 'real_start', 'score', 'cumtime', 'tiebreaker')
+ list_display = (
+ 'contest',
+ 'username',
+ 'show_virtual',
+ 'real_start',
+ 'score',
+ 'cumtime',
+ 'tiebreaker',
+ )
actions = ['recalculate_results']
actions_on_bottom = actions_on_top = True
search_fields = ('contest__key', 'contest__name', 'user__user__username')
@@ -340,9 +518,20 @@ class ContestParticipationAdmin(admin.ModelAdmin):
date_hierarchy = 'real_start'
def get_queryset(self, request):
- return super(ContestParticipationAdmin, self).get_queryset(request).only(
- 'contest__name', 'contest__format_name', 'contest__format_config',
- 'user__user__username', 'real_start', 'score', 'cumtime', 'tiebreaker', 'virtual',
+ return (
+ super(ContestParticipationAdmin, self)
+ .get_queryset(request)
+ .only(
+ 'contest__name',
+ 'contest__format_name',
+ 'contest__format_config',
+ 'user__user__username',
+ 'real_start',
+ 'score',
+ 'cumtime',
+ 'tiebreaker',
+ 'virtual',
+ )
)
def save_model(self, request, obj, form, change):
@@ -356,9 +545,15 @@ def recalculate_results(self, request, queryset):
for participation in queryset:
participation.recompute_results()
count += 1
- self.message_user(request, ngettext('%d participation recalculated.',
- '%d participations recalculated.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d participation recalculated.',
+ '%d participations recalculated.',
+ count,
+ )
+ % count,
+ )
@admin.display(description=_('username'), ordering='user__user__username')
def username(self, obj):
diff --git a/judge/admin/interface.py b/judge/admin/interface.py
index f49c3a654c..8c09797231 100644
--- a/judge/admin/interface.py
+++ b/judge/admin/interface.py
@@ -11,7 +11,11 @@
from judge.dblock import LockModel
from judge.models import BlogPost, NavigationBar
-from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminMartorWidget
+from judge.widgets import (
+ AdminHeavySelect2MultipleWidget,
+ AdminHeavySelect2Widget,
+ AdminMartorWidget,
+)
class NavigationBarAdmin(DraggableMPTTAdmin):
@@ -36,7 +40,9 @@ def save_model(self, request, obj, form, change):
def changelist_view(self, request, extra_context=None):
self.__save_model_calls = 0
with NavigationBar.objects.disable_mptt_updates():
- result = super(NavigationBarAdmin, self).changelist_view(request, extra_context)
+ result = super(NavigationBarAdmin, self).changelist_view(
+ request, extra_context
+ )
if self.__save_model_calls:
with LockModel(write=(NavigationBar,)):
NavigationBar.objects.rebuild()
@@ -45,7 +51,11 @@ def changelist_view(self, request, extra_context=None):
class FlatpageForm(OldFlatpageForm):
class Meta(OldFlatpageForm.Meta):
- widgets = {'content': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('flatpage_preview')})}
+ widgets = {
+ 'content': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('flatpage_preview')}
+ )
+ }
class FlatPageAdmin(VersionAdmin, OldFlatPageAdmin):
@@ -61,15 +71,24 @@ def __init__(self, *args, **kwargs):
class Meta:
widgets = {
- 'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
- 'content': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('blog_preview')}),
- 'summary': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('blog_preview')}),
+ 'authors': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'content': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('blog_preview')}
+ ),
+ 'summary': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('blog_preview')}
+ ),
}
class BlogPostAdmin(VersionAdmin):
fieldsets = (
- (None, {'fields': ('title', 'slug', 'authors', 'visible', 'sticky', 'publish_on')}),
+ (
+ None,
+ {'fields': ('title', 'slug', 'authors', 'visible', 'sticky', 'publish_on')},
+ ),
(_('Content'), {'fields': ('content', 'og_image')}),
(_('Summary'), {'classes': ('collapse',), 'fields': ('summary',)}),
)
@@ -104,15 +123,25 @@ def __init__(self, *args, **kwargs):
class Meta:
widgets = {
- 'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
- 'problem': AdminHeavySelect2Widget(data_view='problem_select2', attrs={'style': 'width: 250px'}),
- 'content': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('solution_preview')}),
+ 'authors': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'problem': AdminHeavySelect2Widget(
+ data_view='problem_select2', attrs={'style': 'width: 250px'}
+ ),
+ 'content': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('solution_preview')}
+ ),
}
class LicenseForm(ModelForm):
class Meta:
- widgets = {'text': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('license_preview')})}
+ widgets = {
+ 'text': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('license_preview')}
+ )
+ }
class LicenseAdmin(admin.ModelAdmin):
@@ -135,7 +164,14 @@ def queryset(self, request, queryset):
class LogEntryAdmin(admin.ModelAdmin):
- readonly_fields = ('user', 'content_type', 'object_id', 'object_repr', 'action_flag', 'change_message')
+ readonly_fields = (
+ 'user',
+ 'content_type',
+ 'object_id',
+ 'object_repr',
+ 'action_flag',
+ 'change_message',
+ )
list_display = ('__str__', 'action_time', 'user', 'content_type', 'object_link')
search_fields = ('object_repr', 'change_message')
list_filter = (UserListFilter, 'content_type')
@@ -158,8 +194,14 @@ def object_link(self, obj):
else:
ct = obj.content_type
try:
- link = format_html('{0} ', obj.object_repr,
- reverse('admin:%s_%s_change' % (ct.app_label, ct.model), args=(obj.object_id,)))
+ link = format_html(
+ '{0} ',
+ obj.object_repr,
+ reverse(
+ 'admin:%s_%s_change' % (ct.app_label, ct.model),
+ args=(obj.object_id,),
+ ),
+ )
except NoReverseMatch:
link = obj.object_repr
return link
diff --git a/judge/admin/organization.py b/judge/admin/organization.py
index ff5fab5f0b..493b9a031c 100644
--- a/judge/admin/organization.py
+++ b/judge/admin/organization.py
@@ -18,7 +18,16 @@ class Meta:
class ClassAdmin(VersionAdmin):
- fields = ('name', 'slug', 'organization', 'is_active', 'access_code', 'admins', 'description', 'members')
+ fields = (
+ 'name',
+ 'slug',
+ 'organization',
+ 'is_active',
+ 'access_code',
+ 'admins',
+ 'description',
+ 'members',
+ )
list_display = ('name', 'organization', 'is_active')
prepopulated_fields = {'slug': ('name',)}
form = ClassForm
@@ -27,22 +36,26 @@ def get_queryset(self, request):
queryset = super().get_queryset(request)
if not request.user.has_perm('judge.edit_all_organization'):
queryset = queryset.filter(
- Q(admins__id=request.profile.id) |
- Q(organization__admins__id=request.profile.id),
+ Q(admins__id=request.profile.id)
+ | Q(organization__admins__id=request.profile.id),
).distinct()
return queryset
def has_add_permission(self, request):
- return (request.user.has_perm('judge.add_class') and
- Organization.objects.filter(admins__id=request.profile.id).exists())
+ return (
+ request.user.has_perm('judge.add_class')
+ and Organization.objects.filter(admins__id=request.profile.id).exists()
+ )
def has_change_permission(self, request, obj=None):
if not request.user.has_perm('judge.change_class'):
return False
if request.user.has_perm('judge.edit_all_organization') or obj is None:
return True
- return (obj.admins.filter(id=request.profile.id).exists() or
- obj.organization.admins.filter(id=request.profile.id).exists())
+ return (
+ obj.admins.filter(id=request.profile.id).exists()
+ or obj.organization.admins.filter(id=request.profile.id).exists()
+ )
def get_readonly_fields(self, request, obj=None):
fields = []
@@ -55,7 +68,9 @@ def get_readonly_fields(self, request, obj=None):
def get_form(self, request, obj=None, change=False, **kwargs):
form = super().get_form(request, obj, change, **kwargs)
if 'organization' in form.base_fields:
- form.base_fields['organization'].queryset = Organization.objects.filter(admins__id=request.profile.id)
+ form.base_fields['organization'].queryset = Organization.objects.filter(
+ admins__id=request.profile.id
+ )
return form
@@ -63,14 +78,26 @@ class OrganizationForm(ModelForm):
class Meta:
widgets = {
'admins': AdminHeavySelect2MultipleWidget(data_view='profile_select2'),
- 'about': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('organization_preview')}),
+ 'about': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('organization_preview')}
+ ),
}
class OrganizationAdmin(VersionAdmin):
readonly_fields = ('creation_date',)
- fields = ('name', 'slug', 'short_name', 'is_open', 'class_required', 'about', 'logo_override_image', 'slots',
- 'creation_date', 'admins')
+ fields = (
+ 'name',
+ 'slug',
+ 'short_name',
+ 'is_open',
+ 'class_required',
+ 'about',
+ 'logo_override_image',
+ 'slots',
+ 'creation_date',
+ 'admins',
+ )
list_display = ('name', 'short_name', 'is_open', 'slots', 'show_public')
prepopulated_fields = {'slug': ('name',)}
actions_on_top = True
@@ -79,8 +106,11 @@ class OrganizationAdmin(VersionAdmin):
@admin.display(description='')
def show_public(self, obj):
- return format_html('{1} ',
- obj.get_absolute_url(), gettext('View on site'))
+ return format_html(
+ '{1} ',
+ obj.get_absolute_url(),
+ gettext('View on site'),
+ )
def get_readonly_fields(self, request, obj=None):
fields = self.readonly_fields
diff --git a/judge/admin/problem.py b/judge/admin/problem.py
index c78c0981f6..b5f0c40d29 100644
--- a/judge/admin/problem.py
+++ b/judge/admin/problem.py
@@ -10,15 +10,29 @@
from django.utils.translation import gettext, gettext_lazy as _, ngettext
from reversion.admin import VersionAdmin
-from judge.models import LanguageLimit, Problem, ProblemClarification, ProblemPointsVote, ProblemTranslation, Profile, \
- Solution
+from judge.models import (
+ LanguageLimit,
+ Problem,
+ ProblemClarification,
+ ProblemPointsVote,
+ ProblemTranslation,
+ Profile,
+ Solution,
+)
from judge.utils.views import NoBatchDeleteMixin
-from judge.widgets import AdminHeavySelect2MultipleWidget, AdminMartorWidget, AdminSelect2MultipleWidget, \
- AdminSelect2Widget, CheckboxSelectMultipleWithSelectAll
+from judge.widgets import (
+ AdminHeavySelect2MultipleWidget,
+ AdminMartorWidget,
+ AdminSelect2MultipleWidget,
+ AdminSelect2Widget,
+ CheckboxSelectMultipleWithSelectAll,
+)
class ProblemForm(ModelForm):
- change_message = forms.CharField(max_length=256, label=_('Edit reason'), required=False)
+ change_message = forms.CharField(
+ max_length=256, label=_('Edit reason'), required=False
+ )
def __init__(self, *args, **kwargs):
super(ProblemForm, self).__init__(*args, **kwargs)
@@ -26,22 +40,34 @@ def __init__(self, *args, **kwargs):
self.fields['curators'].widget.can_add_related = False
self.fields['testers'].widget.can_add_related = False
self.fields['banned_users'].widget.can_add_related = False
- self.fields['change_message'].widget.attrs.update({
- 'placeholder': gettext('Describe the changes you made (optional)'),
- })
+ self.fields['change_message'].widget.attrs.update(
+ {
+ 'placeholder': gettext('Describe the changes you made (optional)'),
+ }
+ )
class Meta:
widgets = {
- 'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
- 'curators': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
- 'testers': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
- 'banned_users': AdminHeavySelect2MultipleWidget(data_view='profile_select2',
- attrs={'style': 'width: 100%'}),
- 'organizations': AdminHeavySelect2MultipleWidget(data_view='organization_select2',
- attrs={'style': 'width: 100%'}),
+ 'authors': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'curators': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'testers': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'banned_users': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'organizations': AdminHeavySelect2MultipleWidget(
+ data_view='organization_select2', attrs={'style': 'width: 100%'}
+ ),
'types': AdminSelect2MultipleWidget,
'group': AdminSelect2Widget,
- 'description': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('problem_preview')}),
+ 'description': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('problem_preview')}
+ ),
}
@@ -49,7 +75,9 @@ class ProblemCreatorListFilter(admin.SimpleListFilter):
title = parameter_name = 'creator'
def lookups(self, request, model_admin):
- queryset = Profile.objects.exclude(authored_problems=None).values_list('user__username', flat=True)
+ queryset = Profile.objects.exclude(authored_problems=None).values_list(
+ 'user__username', flat=True
+ )
return [(name, name) for name in queryset]
def queryset(self, request, queryset):
@@ -71,7 +99,11 @@ class LanguageLimitInline(admin.TabularInline):
class ProblemClarificationForm(ModelForm):
class Meta:
- widgets = {'description': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('comment_preview')})}
+ widgets = {
+ 'description': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('comment_preview')}
+ )
+ }
class ProblemClarificationInline(admin.StackedInline):
@@ -88,8 +120,12 @@ def __init__(self, *args, **kwargs):
class Meta:
widgets = {
- 'authors': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
- 'content': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('solution_preview')}),
+ 'authors': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'content': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('solution_preview')}
+ ),
}
@@ -102,7 +138,11 @@ class ProblemSolutionInline(admin.StackedInline):
class ProblemTranslationForm(ModelForm):
class Meta:
- widgets = {'description': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('problem_preview')})}
+ widgets = {
+ 'description': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('problem_preview')}
+ )
+ }
class ProblemTranslationInline(admin.StackedInline):
@@ -114,21 +154,41 @@ class ProblemTranslationInline(admin.StackedInline):
def has_permission_full_markup(self, request, obj=None):
if not obj:
return True
- return request.user.has_perm('judge.problem_full_markup') or not obj.is_full_markup
+ return (
+ request.user.has_perm('judge.problem_full_markup') or not obj.is_full_markup
+ )
- has_add_permission = has_change_permission = has_delete_permission = has_permission_full_markup
+ has_add_permission = (
+ has_change_permission
+ ) = has_delete_permission = has_permission_full_markup
class ProblemAdmin(NoBatchDeleteMixin, VersionAdmin):
fieldsets = (
- (None, {
- 'fields': (
- 'code', 'name', 'is_public', 'is_manually_managed', 'date', 'authors', 'curators', 'testers',
- 'organizations', 'submission_source_visibility_mode', 'is_full_markup',
- 'description', 'license',
- ),
- }),
- (_('Social Media'), {'classes': ('collapse',), 'fields': ('og_image', 'summary')}),
+ (
+ None,
+ {
+ 'fields': (
+ 'code',
+ 'name',
+ 'is_public',
+ 'is_manually_managed',
+ 'date',
+ 'authors',
+ 'curators',
+ 'testers',
+ 'organizations',
+ 'submission_source_visibility_mode',
+ 'is_full_markup',
+ 'description',
+ 'license',
+ ),
+ },
+ ),
+ (
+ _('Social Media'),
+ {'classes': ('collapse',), 'fields': ('og_image', 'summary')},
+ ),
(_('Taxonomy'), {'fields': ('types', 'group')}),
(_('Points'), {'fields': (('points', 'partial'), 'short_circuit')}),
(_('Limits'), {'fields': ('time_limit', 'memory_limit')}),
@@ -136,10 +196,27 @@ class ProblemAdmin(NoBatchDeleteMixin, VersionAdmin):
(_('Justice'), {'fields': ('banned_users',)}),
(_('History'), {'fields': ('change_message',)}),
)
- list_display = ['code', 'name', 'show_authors', 'points', 'is_public', 'show_public']
+ list_display = [
+ 'code',
+ 'name',
+ 'show_authors',
+ 'points',
+ 'is_public',
+ 'show_public',
+ ]
ordering = ['code']
- search_fields = ('code', 'name', 'authors__user__username', 'curators__user__username')
- inlines = [LanguageLimitInline, ProblemClarificationInline, ProblemSolutionInline, ProblemTranslationInline]
+ search_fields = (
+ 'code',
+ 'name',
+ 'authors__user__username',
+ 'curators__user__username',
+ )
+ inlines = [
+ LanguageLimitInline,
+ ProblemClarificationInline,
+ ProblemSolutionInline,
+ ProblemTranslationInline,
+ ]
list_max_show_all = 1000
actions_on_top = True
actions_on_bottom = True
@@ -180,39 +257,64 @@ def show_authors(self, obj):
@admin.display(description='')
def show_public(self, obj):
- return format_html('{0} ', gettext('View on site'), obj.get_absolute_url())
+ return format_html(
+ '{0} ', gettext('View on site'), obj.get_absolute_url()
+ )
def _rescore(self, request, problem_id):
from judge.tasks import rescore_problem
+
transaction.on_commit(rescore_problem.s(problem_id).delay)
@admin.display(description=_('Set publish date to now'))
def update_publish_date(self, request, queryset):
count = queryset.update(date=timezone.now())
- self.message_user(request, ngettext("%d problem's publish date successfully updated.",
- "%d problems' publish date successfully updated.",
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ "%d problem's publish date successfully updated.",
+ "%d problems' publish date successfully updated.",
+ count,
+ )
+ % count,
+ )
@admin.display(description=_('Mark problems as public'))
def make_public(self, request, queryset):
count = queryset.update(is_public=True)
for problem_id in queryset.values_list('id', flat=True):
self._rescore(request, problem_id)
- self.message_user(request, ngettext('%d problem successfully marked as public.',
- '%d problems successfully marked as public.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d problem successfully marked as public.',
+ '%d problems successfully marked as public.',
+ count,
+ )
+ % count,
+ )
@admin.display(description=_('Mark problems as private'))
def make_private(self, request, queryset):
count = queryset.update(is_public=False)
for problem_id in queryset.values_list('id', flat=True):
self._rescore(request, problem_id)
- self.message_user(request, ngettext('%d problem successfully marked as private.',
- '%d problems successfully marked as private.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d problem successfully marked as private.',
+ '%d problems successfully marked as private.',
+ count,
+ )
+ % count,
+ )
def get_queryset(self, request):
- return Problem.get_editable_problems(request.user).prefetch_related('authors__user').distinct()
+ return (
+ Problem.get_editable_problems(request.user)
+ .prefetch_related('authors__user')
+ .distinct()
+ )
def has_change_permission(self, request, obj=None):
if obj is None:
@@ -222,7 +324,9 @@ def has_change_permission(self, request, obj=None):
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == 'allowed_languages':
kwargs['widget'] = CheckboxSelectMultipleWithSelectAll()
- return super(ProblemAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
+ return super(ProblemAdmin, self).formfield_for_manytomany(
+ db_field, request, **kwargs
+ )
def get_form(self, *args, **kwargs):
form = super(ProblemAdmin, self).get_form(*args, **kwargs)
@@ -234,16 +338,18 @@ def save_model(self, request, obj, form, change):
if form.changed_data and 'organizations' in form.changed_data:
obj.is_organization_private = bool(form.cleaned_data['organizations'])
super(ProblemAdmin, self).save_model(request, obj, form, change)
- if (
- form.changed_data and
- any(f in form.changed_data for f in ('is_public', 'organizations', 'points', 'partial'))
+ if form.changed_data and any(
+ f in form.changed_data
+ for f in ('is_public', 'organizations', 'points', 'partial')
):
self._rescore(request, obj.id)
def construct_change_message(self, request, form, *args, **kwargs):
if form.cleaned_data.get('change_message'):
return form.cleaned_data['change_message']
- return super(ProblemAdmin, self).construct_change_message(request, form, *args, **kwargs)
+ return super(ProblemAdmin, self).construct_change_message(
+ request, form, *args, **kwargs
+ )
class ProblemPointsVoteAdmin(admin.ModelAdmin):
@@ -252,7 +358,9 @@ class ProblemPointsVoteAdmin(admin.ModelAdmin):
readonly_fields = ('voter', 'problem', 'vote_time')
def get_queryset(self, request):
- return ProblemPointsVote.objects.filter(problem__in=Problem.get_editable_problems(request.user))
+ return ProblemPointsVote.objects.filter(
+ problem__in=Problem.get_editable_problems(request.user)
+ )
def has_add_permission(self, request):
return False
diff --git a/judge/admin/profile.py b/judge/admin/profile.py
index 5e267bf93e..fa2c829359 100644
--- a/judge/admin/profile.py
+++ b/judge/admin/profile.py
@@ -17,10 +17,16 @@ def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
if 'current_contest' in self.base_fields:
# form.fields['current_contest'] does not exist when the user has only view permission on the model.
- self.fields['current_contest'].queryset = self.instance.contest_history.select_related('contest') \
- .only('contest__name', 'user_id', 'virtual')
- self.fields['current_contest'].label_from_instance = \
- lambda obj: '%s v%d' % (obj.contest.name, obj.virtual) if obj.virtual else obj.contest.name
+ self.fields[
+ 'current_contest'
+ ].queryset = self.instance.contest_history.select_related('contest').only(
+ 'contest__name', 'user_id', 'virtual'
+ )
+ self.fields['current_contest'].label_from_instance = (
+ lambda obj: '%s v%d' % (obj.contest.name, obj.virtual)
+ if obj.virtual
+ else obj.contest.name
+ )
class Meta:
widgets = {
@@ -28,7 +34,9 @@ class Meta:
'language': AdminSelect2Widget,
'ace_theme': AdminSelect2Widget,
'current_contest': AdminSelect2Widget,
- 'about': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('profile_preview')}),
+ 'about': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('profile_preview')}
+ ),
}
@@ -37,7 +45,11 @@ class TimezoneFilter(admin.SimpleListFilter):
parameter_name = 'timezone'
def lookups(self, request, model_admin):
- return Profile.objects.values_list('timezone', 'timezone').distinct().order_by('timezone')
+ return (
+ Profile.objects.values_list('timezone', 'timezone')
+ .distinct()
+ .order_by('timezone')
+ )
def queryset(self, request, queryset):
if self.value() is None:
@@ -55,12 +67,37 @@ def has_add_permission(self, request, obj=None):
class ProfileAdmin(NoBatchDeleteMixin, VersionAdmin):
- fields = ('user', 'display_rank', 'about', 'organizations', 'timezone', 'language', 'ace_theme',
- 'math_engine', 'last_access', 'ip', 'mute', 'is_unlisted', 'is_banned_from_problem_voting',
- 'username_display_override', 'notes', 'is_totp_enabled', 'user_script', 'current_contest')
+ fields = (
+ 'user',
+ 'display_rank',
+ 'about',
+ 'organizations',
+ 'timezone',
+ 'language',
+ 'ace_theme',
+ 'math_engine',
+ 'last_access',
+ 'ip',
+ 'mute',
+ 'is_unlisted',
+ 'is_banned_from_problem_voting',
+ 'username_display_override',
+ 'notes',
+ 'is_totp_enabled',
+ 'user_script',
+ 'current_contest',
+ )
readonly_fields = ('user',)
- list_display = ('admin_user_admin', 'email', 'is_totp_enabled', 'timezone_full',
- 'date_joined', 'last_access', 'ip', 'show_public')
+ list_display = (
+ 'admin_user_admin',
+ 'email',
+ 'is_totp_enabled',
+ 'timezone_full',
+ 'date_joined',
+ 'last_access',
+ 'ip',
+ 'show_public',
+ )
ordering = ('user__username',)
search_fields = ('user__username', 'ip', 'user__email')
list_filter = ('language', TimezoneFilter)
@@ -101,8 +138,11 @@ def get_readonly_fields(self, request, obj=None):
@admin.display(description='')
def show_public(self, obj):
- return format_html('{1} ',
- obj.get_absolute_url(), gettext('View on site'))
+ return format_html(
+ '{1} ',
+ obj.get_absolute_url(),
+ gettext('View on site'),
+ )
@admin.display(description=_('user'), ordering='user__username')
def admin_user_admin(self, obj):
@@ -126,16 +166,23 @@ def recalculate_points(self, request, queryset):
for profile in queryset:
profile.calculate_points()
count += 1
- self.message_user(request, ngettext('%d user had scores recalculated.',
- '%d users had scores recalculated.',
- count) % count)
+ self.message_user(
+ request,
+ ngettext(
+ '%d user had scores recalculated.',
+ '%d users had scores recalculated.',
+ count,
+ )
+ % count,
+ )
def get_form(self, request, obj=None, **kwargs):
form = super(ProfileAdmin, self).get_form(request, obj, **kwargs)
if 'user_script' in form.base_fields:
# form.base_fields['user_script'] does not exist when the user has only view permission on the model.
form.base_fields['user_script'].widget = AceWidget(
- mode='javascript', theme=request.profile.resolved_ace_theme,
+ mode='javascript',
+ theme=request.profile.resolved_ace_theme,
)
return form
diff --git a/judge/admin/runtime.py b/judge/admin/runtime.py
index 3c756527c8..f1704bc784 100644
--- a/judge/admin/runtime.py
+++ b/judge/admin/runtime.py
@@ -19,8 +19,18 @@ class Meta:
class LanguageAdmin(VersionAdmin):
- fields = ('key', 'name', 'short_name', 'common_name', 'ace', 'pygments', 'info', 'extension', 'description',
- 'template')
+ fields = (
+ 'key',
+ 'name',
+ 'short_name',
+ 'common_name',
+ 'ace',
+ 'pygments',
+ 'info',
+ 'extension',
+ 'description',
+ 'template',
+ )
list_display = ('key', 'name', 'common_name', 'info')
form = LanguageForm
@@ -28,7 +38,8 @@ def get_form(self, request, obj=None, **kwargs):
form = super(LanguageAdmin, self).get_form(request, obj, **kwargs)
if obj is not None:
form.base_fields['template'].widget = AceWidget(
- mode=obj.ace, theme=request.profile.resolved_ace_theme,
+ mode=obj.ace,
+ theme=request.profile.resolved_ace_theme,
)
return form
@@ -36,8 +47,10 @@ def get_form(self, request, obj=None, **kwargs):
class GenerateKeyTextInput(TextInput):
def render(self, name, value, attrs=None, renderer=None):
text = super(TextInput, self).render(name, value, attrs)
- return mark_safe(text + format_html(
- """\
+ return mark_safe(
+ text
+ + format_html(
+ """\
{1}
-""", name, _('Regenerate')))
+""",
+ name,
+ _('Regenerate'),
+ )
+ )
class JudgeAdminForm(ModelForm):
@@ -59,25 +76,52 @@ class Meta:
class JudgeAdmin(VersionAdmin):
form = JudgeAdminForm
- readonly_fields = ('created', 'online', 'start_time', 'ping', 'load', 'last_ip', 'runtimes', 'problems',
- 'is_disabled')
+ readonly_fields = (
+ 'created',
+ 'online',
+ 'start_time',
+ 'ping',
+ 'load',
+ 'last_ip',
+ 'runtimes',
+ 'problems',
+ 'is_disabled',
+ )
fieldsets = (
(None, {'fields': ('name', 'auth_key', 'is_blocked', 'is_disabled')}),
(_('Description'), {'fields': ('description',)}),
- (_('Information'), {'fields': ('created', 'online', 'last_ip', 'start_time', 'ping', 'load')}),
+ (
+ _('Information'),
+ {'fields': ('created', 'online', 'last_ip', 'start_time', 'ping', 'load')},
+ ),
(_('Capabilities'), {'fields': ('runtimes',)}),
)
- list_display = ('name', 'online', 'is_disabled', 'start_time', 'ping', 'load', 'last_ip')
+ list_display = (
+ 'name',
+ 'online',
+ 'is_disabled',
+ 'start_time',
+ 'ping',
+ 'load',
+ 'last_ip',
+ )
ordering = ['-online', 'name']
formfield_overrides = {
TextField: {'widget': AdminMartorWidget},
}
def get_urls(self):
- return ([path('/disconnect/', self.disconnect_view, name='judge_judge_disconnect'),
- path('/terminate/', self.terminate_view, name='judge_judge_terminate'),
- path('/disable/', self.disable_view, name='judge_judge_disable')] +
- super(JudgeAdmin, self).get_urls())
+ return [
+ path(
+ '/disconnect/',
+ self.disconnect_view,
+ name='judge_judge_disconnect',
+ ),
+ path(
+ '/terminate/', self.terminate_view, name='judge_judge_terminate'
+ ),
+ path('/disable/', self.disable_view, name='judge_judge_disable'),
+ ] + super(JudgeAdmin, self).get_urls()
def disconnect_judge(self, id, force=False):
judge = get_object_or_404(Judge, id=id)
@@ -93,7 +137,9 @@ def terminate_view(self, request, id):
def disable_view(self, request, id):
judge = get_object_or_404(Judge, id=id)
judge.toggle_disabled()
- return HttpResponseRedirect(reverse('admin:judge_judge_change', args=(judge.id,)))
+ return HttpResponseRedirect(
+ reverse('admin:judge_judge_change', args=(judge.id,))
+ )
def get_readonly_fields(self, request, obj=None):
if obj is not None and obj.online:
diff --git a/judge/admin/submission.py b/judge/admin/submission.py
index aa3889d304..be009a68e9 100644
--- a/judge/admin/submission.py
+++ b/judge/admin/submission.py
@@ -14,14 +14,25 @@
from reversion.admin import VersionAdmin
from django_ace import AceWidget
-from judge.models import ContestParticipation, ContestProblem, ContestSubmission, Profile, Submission, \
- SubmissionSource, SubmissionTestCase
+from judge.models import (
+ ContestParticipation,
+ ContestProblem,
+ ContestSubmission,
+ Profile,
+ Submission,
+ SubmissionSource,
+ SubmissionTestCase,
+)
from judge.utils.raw_sql import use_straight_join
class SubmissionStatusFilter(admin.SimpleListFilter):
parameter_name = title = 'status'
- __lookups = (('None', _('None')), ('NotDone', _('Not done')), ('EX', _('Exceptional'))) + Submission.STATUS
+ __lookups = (
+ ('None', _('None')),
+ ('NotDone', _('Not done')),
+ ('EX', _('Exceptional')),
+ ) + Submission.STATUS
__handles = set(map(itemgetter(0), Submission.STATUS))
def lookups(self, request, model_admin):
@@ -68,7 +79,9 @@ class ContestSubmissionInline(admin.StackedInline):
model = ContestSubmission
def get_formset(self, request, obj=None, **kwargs):
- kwargs['formfield_callback'] = partial(self.formfield_for_dbfield, request=request, obj=obj)
+ kwargs['formfield_callback'] = partial(
+ self.formfield_for_dbfield, request=request, obj=obj
+ )
return super(ContestSubmissionInline, self).get_formset(request, obj, **kwargs)
def formfield_for_dbfield(self, db_field, **kwargs):
@@ -76,25 +89,34 @@ def formfield_for_dbfield(self, db_field, **kwargs):
label = None
if submission:
if db_field.name == 'participation':
- kwargs['queryset'] = ContestParticipation.objects.filter(user=submission.user,
- contest__problems=submission.problem) \
- .only('id', 'contest__name', 'virtual')
+ kwargs['queryset'] = ContestParticipation.objects.filter(
+ user=submission.user, contest__problems=submission.problem
+ ).only('id', 'contest__name', 'virtual')
def label(obj):
if obj.spectate:
return gettext('%s (spectating)') % obj.contest.name
if obj.virtual:
- return gettext('%s (virtual %d)') % (obj.contest.name, obj.virtual)
+ return gettext('%s (virtual %d)') % (
+ obj.contest.name,
+ obj.virtual,
+ )
return obj.contest.name
+
elif db_field.name == 'problem':
- kwargs['queryset'] = ContestProblem.objects.filter(problem=submission.problem) \
- .only('id', 'problem__name', 'contest__name')
+ kwargs['queryset'] = ContestProblem.objects.filter(
+ problem=submission.problem
+ ).only('id', 'problem__name', 'contest__name')
def label(obj):
return pgettext('contest problem', '%(problem)s in %(contest)s') % {
- 'problem': obj.problem.name, 'contest': obj.contest.name,
+ 'problem': obj.problem.name,
+ 'contest': obj.contest.name,
}
- field = super(ContestSubmissionInline, self).formfield_for_dbfield(db_field, **kwargs)
+
+ field = super(ContestSubmissionInline, self).formfield_for_dbfield(
+ db_field, **kwargs
+ )
if label is not None:
field.label_from_instance = label
return field
@@ -108,23 +130,54 @@ class SubmissionSourceInline(admin.StackedInline):
def get_formset(self, request, obj=None, **kwargs):
kwargs.setdefault('widgets', {})['source'] = AceWidget(
- mode=obj and obj.language.ace, theme=request.profile.resolved_ace_theme,
+ mode=obj and obj.language.ace,
+ theme=request.profile.resolved_ace_theme,
)
return super().get_formset(request, obj, **kwargs)
class SubmissionAdmin(VersionAdmin):
readonly_fields = ('user', 'problem', 'date', 'judged_date')
- fields = ('user', 'problem', 'date', 'judged_date', 'locked_after', 'time', 'memory', 'points', 'language',
- 'status', 'result', 'case_points', 'case_total', 'judged_on', 'error')
+ fields = (
+ 'user',
+ 'problem',
+ 'date',
+ 'judged_date',
+ 'locked_after',
+ 'time',
+ 'memory',
+ 'points',
+ 'language',
+ 'status',
+ 'result',
+ 'case_points',
+ 'case_total',
+ 'judged_on',
+ 'error',
+ )
actions = ('judge', 'recalculate_score')
- list_display = ('id', 'problem_code', 'problem_name', 'user_column', 'execution_time', 'pretty_memory',
- 'points', 'language_column', 'status', 'result', 'judge_column')
+ list_display = (
+ 'id',
+ 'problem_code',
+ 'problem_name',
+ 'user_column',
+ 'execution_time',
+ 'pretty_memory',
+ 'points',
+ 'language_column',
+ 'status',
+ 'result',
+ 'judge_column',
+ )
list_filter = ('language', SubmissionStatusFilter, SubmissionResultFilter)
search_fields = ('problem__code', 'problem__name', 'user__user__username')
actions_on_top = True
actions_on_bottom = True
- inlines = [SubmissionSourceInline, SubmissionTestCaseInline, ContestSubmissionInline]
+ inlines = [
+ SubmissionSourceInline,
+ SubmissionTestCaseInline,
+ ContestSubmissionInline,
+ ]
def get_readonly_fields(self, request, obj=None):
fields = self.readonly_fields
@@ -133,14 +186,25 @@ def get_readonly_fields(self, request, obj=None):
return fields
def get_queryset(self, request):
- queryset = Submission.objects.select_related('problem', 'user__user', 'language').only(
- 'problem__code', 'problem__name', 'user__user__username', 'language__name',
- 'time', 'memory', 'points', 'status', 'result',
+ queryset = Submission.objects.select_related(
+ 'problem', 'user__user', 'language'
+ ).only(
+ 'problem__code',
+ 'problem__name',
+ 'user__user__username',
+ 'language__name',
+ 'time',
+ 'memory',
+ 'points',
+ 'status',
+ 'result',
)
use_straight_join(queryset)
if not request.user.has_perm('judge.edit_all_problem'):
id = request.profile.id
- queryset = queryset.filter(Q(problem__authors__id=id) | Q(problem__curators__id=id)).distinct()
+ queryset = queryset.filter(
+ Q(problem__authors__id=id) | Q(problem__curators__id=id)
+ ).distinct()
return queryset
def has_add_permission(self, request):
@@ -154,58 +218,111 @@ def has_change_permission(self, request, obj=None):
return obj.problem.is_editor(request.profile)
def lookup_allowed(self, key, value):
- return super(SubmissionAdmin, self).lookup_allowed(key, value) or key in ('problem__code',)
+ return super(SubmissionAdmin, self).lookup_allowed(key, value) or key in (
+ 'problem__code',
+ )
@admin.display(description=_('Rejudge the selected submissions'))
def judge(self, request, queryset):
- if not request.user.has_perm('judge.rejudge_submission') or not request.user.has_perm('judge.edit_own_problem'):
- self.message_user(request, gettext('You do not have the permission to rejudge submissions.'),
- level=messages.ERROR)
+ if not request.user.has_perm(
+ 'judge.rejudge_submission'
+ ) or not request.user.has_perm('judge.edit_own_problem'):
+ self.message_user(
+ request,
+ gettext('You do not have the permission to rejudge submissions.'),
+ level=messages.ERROR,
+ )
return
queryset = queryset.order_by('id')
- if not request.user.has_perm('judge.rejudge_submission_lot') and \
- queryset.count() > settings.DMOJ_SUBMISSIONS_REJUDGE_LIMIT:
- self.message_user(request, gettext('You do not have the permission to rejudge THAT many submissions.'),
- level=messages.ERROR)
+ if (
+ not request.user.has_perm('judge.rejudge_submission_lot')
+ and queryset.count() > settings.DMOJ_SUBMISSIONS_REJUDGE_LIMIT
+ ):
+ self.message_user(
+ request,
+ gettext(
+ 'You do not have the permission to rejudge THAT many submissions.'
+ ),
+ level=messages.ERROR,
+ )
return
if not request.user.has_perm('judge.edit_all_problem'):
id = request.profile.id
- queryset = queryset.filter(Q(problem__authors__id=id) | Q(problem__curators__id=id))
+ queryset = queryset.filter(
+ Q(problem__authors__id=id) | Q(problem__curators__id=id)
+ )
judged = len(queryset)
for model in queryset:
model.judge(rejudge=True, batch_rejudge=True, rejudge_user=request.user)
- self.message_user(request, ngettext('%d submission was successfully scheduled for rejudging.',
- '%d submissions were successfully scheduled for rejudging.',
- judged) % judged)
+ self.message_user(
+ request,
+ ngettext(
+ '%d submission was successfully scheduled for rejudging.',
+ '%d submissions were successfully scheduled for rejudging.',
+ judged,
+ )
+ % judged,
+ )
@admin.display(description=_('Rescore the selected submissions'))
def recalculate_score(self, request, queryset):
if not request.user.has_perm('judge.rejudge_submission'):
- self.message_user(request, gettext('You do not have the permission to rejudge submissions.'),
- level=messages.ERROR)
+ self.message_user(
+ request,
+ gettext('You do not have the permission to rejudge submissions.'),
+ level=messages.ERROR,
+ )
return
- submissions = list(queryset.defer(None).select_related(None).select_related('problem')
- .only('points', 'case_points', 'case_total', 'problem__partial', 'problem__points'))
+ submissions = list(
+ queryset.defer(None)
+ .select_related(None)
+ .select_related('problem')
+ .only(
+ 'points',
+ 'case_points',
+ 'case_total',
+ 'problem__partial',
+ 'problem__points',
+ )
+ )
for submission in submissions:
- submission.points = round(submission.case_points / submission.case_total * submission.problem.points
- if submission.case_total else 0, 1)
- if not submission.problem.partial and submission.points < submission.problem.points:
+ submission.points = round(
+ submission.case_points
+ / submission.case_total
+ * submission.problem.points
+ if submission.case_total
+ else 0,
+ 1,
+ )
+ if (
+ not submission.problem.partial
+ and submission.points < submission.problem.points
+ ):
submission.points = 0
submission.save()
submission.update_contest()
- for profile in Profile.objects.filter(id__in=queryset.values_list('user_id', flat=True).distinct()):
+ for profile in Profile.objects.filter(
+ id__in=queryset.values_list('user_id', flat=True).distinct()
+ ):
profile.calculate_points()
cache.delete('user_complete:%d' % profile.id)
cache.delete('user_attempted:%d' % profile.id)
for participation in ContestParticipation.objects.filter(
- id__in=queryset.values_list('contest__participation_id')).prefetch_related('contest'):
+ id__in=queryset.values_list('contest__participation_id')
+ ).prefetch_related('contest'):
participation.recompute_results()
- self.message_user(request, ngettext('%d submission was successfully rescored.',
- '%d submissions were successfully rescored.',
- len(submissions)) % len(submissions))
+ self.message_user(
+ request,
+ ngettext(
+ '%d submission was successfully rescored.',
+ '%d submissions were successfully rescored.',
+ len(submissions),
+ )
+ % len(submissions),
+ )
@admin.display(description=_('problem code'), ordering='problem__code')
def problem_code(self, obj):
@@ -240,10 +357,15 @@ def language_column(self, obj):
@admin.display(description='')
def judge_column(self, obj):
if obj.is_locked:
- return format_html(' ', _('Locked'))
+ return format_html(
+ ' ', _('Locked')
+ )
else:
- return format_html(' ', _('Rejudge'),
- reverse('admin:judge_submission_rejudge', args=(obj.id,)))
+ return format_html(
+ ' ',
+ _('Rejudge'),
+ reverse('admin:judge_submission_rejudge', args=(obj.id,)),
+ )
def get_urls(self):
return [
@@ -251,11 +373,14 @@ def get_urls(self):
] + super(SubmissionAdmin, self).get_urls()
def judge_view(self, request, id):
- if not request.user.has_perm('judge.rejudge_submission') or not request.user.has_perm('judge.edit_own_problem'):
+ if not request.user.has_perm(
+ 'judge.rejudge_submission'
+ ) or not request.user.has_perm('judge.edit_own_problem'):
raise PermissionDenied()
submission = get_object_or_404(Submission, id=id)
- if not request.user.has_perm('judge.edit_all_problem') and \
- not submission.problem.is_editor(request.profile):
+ if not request.user.has_perm(
+ 'judge.edit_all_problem'
+ ) and not submission.problem.is_editor(request.profile):
raise PermissionDenied()
submission.judge(rejudge=True, rejudge_user=request.user)
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
diff --git a/judge/admin/taxon.py b/judge/admin/taxon.py
index aa245d7dc7..d0a69370eb 100644
--- a/judge/admin/taxon.py
+++ b/judge/admin/taxon.py
@@ -12,7 +12,8 @@ class ProblemGroupForm(ModelForm):
queryset=Problem.objects.all(),
required=False,
help_text=_('These problems are included in this group of problems.'),
- widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'))
+ widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'),
+ )
class ProblemGroupAdmin(admin.ModelAdmin):
@@ -25,7 +26,9 @@ def save_model(self, request, obj, form, change):
obj.save()
def get_form(self, request, obj=None, **kwargs):
- self.form.base_fields['problems'].initial = [o.pk for o in obj.problem_set.all()] if obj else []
+ self.form.base_fields['problems'].initial = (
+ [o.pk for o in obj.problem_set.all()] if obj else []
+ )
return super(ProblemGroupAdmin, self).get_form(request, obj, **kwargs)
@@ -35,7 +38,8 @@ class ProblemTypeForm(ModelForm):
queryset=Problem.objects.all(),
required=False,
help_text=_('These problems are included in this type of problems.'),
- widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'))
+ widget=AdminHeavySelect2MultipleWidget(data_view='problem_select2'),
+ )
class ProblemTypeAdmin(admin.ModelAdmin):
@@ -48,5 +52,7 @@ def save_model(self, request, obj, form, change):
obj.save()
def get_form(self, request, obj=None, **kwargs):
- self.form.base_fields['problems'].initial = [o.pk for o in obj.problem_set.all()] if obj else []
+ self.form.base_fields['problems'].initial = (
+ [o.pk for o in obj.problem_set.all()] if obj else []
+ )
return super(ProblemTypeAdmin, self).get_form(request, obj, **kwargs)
diff --git a/judge/admin/ticket.py b/judge/admin/ticket.py
index 737bee53cd..4507fe5498 100644
--- a/judge/admin/ticket.py
+++ b/judge/admin/ticket.py
@@ -4,14 +4,22 @@
from django.urls import reverse_lazy
from judge.models import TicketMessage
-from judge.widgets import AdminHeavySelect2MultipleWidget, AdminHeavySelect2Widget, AdminMartorWidget
+from judge.widgets import (
+ AdminHeavySelect2MultipleWidget,
+ AdminHeavySelect2Widget,
+ AdminMartorWidget,
+)
class TicketMessageForm(ModelForm):
class Meta:
widgets = {
- 'user': AdminHeavySelect2Widget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
- 'body': AdminMartorWidget(attrs={'data-markdownfy-url': reverse_lazy('ticket_preview')}),
+ 'user': AdminHeavySelect2Widget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'body': AdminMartorWidget(
+ attrs={'data-markdownfy-url': reverse_lazy('ticket_preview')}
+ ),
}
@@ -24,13 +32,25 @@ class TicketMessageInline(StackedInline):
class TicketForm(ModelForm):
class Meta:
widgets = {
- 'user': AdminHeavySelect2Widget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
- 'assignees': AdminHeavySelect2MultipleWidget(data_view='profile_select2', attrs={'style': 'width: 100%'}),
+ 'user': AdminHeavySelect2Widget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
+ 'assignees': AdminHeavySelect2MultipleWidget(
+ data_view='profile_select2', attrs={'style': 'width: 100%'}
+ ),
}
class TicketAdmin(ModelAdmin):
- fields = ('title', 'time', 'user', 'assignees', 'content_type', 'object_id', 'notes')
+ fields = (
+ 'title',
+ 'time',
+ 'user',
+ 'assignees',
+ 'content_type',
+ 'object_id',
+ 'notes',
+ )
readonly_fields = ('time',)
list_display = ('title', 'user', 'time', 'linked_item')
inlines = [TicketMessageInline]
diff --git a/judge/bridge/base_handler.py b/judge/bridge/base_handler.py
index e3ed9c574a..85ae2685b1 100644
--- a/judge/bridge/base_handler.py
+++ b/judge/bridge/base_handler.py
@@ -70,8 +70,12 @@ def timeout(self, timeout):
def read_sized_packet(self, size, initial=None):
if size > MAX_ALLOWED_PACKET_SIZE:
- logger.log(logging.WARNING if self._got_packet else logging.INFO,
- 'Disconnecting client due to too-large message size (%d bytes): %s', size, self.client_address)
+ logger.log(
+ logging.WARNING if self._got_packet else logging.INFO,
+ 'Disconnecting client due to too-large message size (%d bytes): %s',
+ size,
+ self.client_address,
+ )
raise Disconnect()
buffer = []
@@ -149,7 +153,9 @@ def handle(self):
tag = self.read_size()
self._initial_tag = size_pack.pack(tag)
if self.client_address[0] in self.proxies and self._initial_tag == b'PROX':
- proxy, _, remainder = self.read_proxy_header(self._initial_tag).partition(b'\r\n')
+ proxy, _, remainder = self.read_proxy_header(
+ self._initial_tag
+ ).partition(b'\r\n')
self.parse_proxy_protocol(proxy)
while remainder:
@@ -157,8 +163,8 @@ def handle(self):
self.read_sized_packet(self.read_size(remainder))
break
- size = size_pack.unpack(remainder[:size_pack.size])[0]
- remainder = remainder[size_pack.size:]
+ size = size_pack.unpack(remainder[: size_pack.size])[0]
+ remainder = remainder[size_pack.size :]
if len(remainder) <= size:
self.read_sized_packet(size, remainder)
break
@@ -174,17 +180,28 @@ def handle(self):
return
except zlib.error:
if self._got_packet:
- logger.warning('Encountered zlib error during packet handling, disconnecting client: %s',
- self.client_address, exc_info=True)
+ logger.warning(
+ 'Encountered zlib error during packet handling, disconnecting client: %s',
+ self.client_address,
+ exc_info=True,
+ )
else:
- logger.info('Potentially wrong protocol (zlib error): %s: %r', self.client_address, self._initial_tag,
- exc_info=True)
+ logger.info(
+ 'Potentially wrong protocol (zlib error): %s: %r',
+ self.client_address,
+ self._initial_tag,
+ exc_info=True,
+ )
except socket.timeout:
if self._got_packet:
logger.info('Socket timed out: %s', self.client_address)
self.on_timeout()
else:
- logger.info('Potentially wrong protocol: %s: %r', self.client_address, self._initial_tag)
+ logger.info(
+ 'Potentially wrong protocol: %s: %r',
+ self.client_address,
+ self._initial_tag,
+ )
except socket.error as e:
# When a gevent socket is shutdown, gevent cancels all waits, causing recv to raise cancel_wait_ex.
if e.__class__.__name__ == 'cancel_wait_ex':
diff --git a/judge/bridge/daemon.py b/judge/bridge/daemon.py
index ce8a702dbd..048aa4aecc 100644
--- a/judge/bridge/daemon.py
+++ b/judge/bridge/daemon.py
@@ -20,12 +20,17 @@ def reset_judges():
def judge_daemon():
reset_judges()
- Submission.objects.filter(status__in=Submission.IN_PROGRESS_GRADING_STATUS) \
- .update(status='IE', result='IE', error=None)
+ Submission.objects.filter(status__in=Submission.IN_PROGRESS_GRADING_STATUS).update(
+ status='IE', result='IE', error=None
+ )
judges = JudgeList()
- judge_server = Server(settings.BRIDGED_JUDGE_ADDRESS, partial(JudgeHandler, judges=judges))
- django_server = Server(settings.BRIDGED_DJANGO_ADDRESS, partial(DjangoHandler, judges=judges))
+ judge_server = Server(
+ settings.BRIDGED_JUDGE_ADDRESS, partial(JudgeHandler, judges=judges)
+ )
+ django_server = Server(
+ settings.BRIDGED_DJANGO_ADDRESS, partial(DjangoHandler, judges=judges)
+ )
threading.Thread(target=django_server.serve_forever).start()
threading.Thread(target=judge_server.serve_forever).start()
diff --git a/judge/bridge/django_handler.py b/judge/bridge/django_handler.py
index cdde06e0dd..515eeee7b5 100644
--- a/judge/bridge/django_handler.py
+++ b/judge/bridge/django_handler.py
@@ -28,7 +28,9 @@ def send(self, data):
def on_packet(self, packet):
packet = json.loads(packet)
try:
- result = self.handlers.get(packet.get('name', None), self.on_malformed)(packet)
+ result = self.handlers.get(packet.get('name', None), self.on_malformed)(
+ packet
+ )
except Exception:
logger.exception('Error in packet handling (Django-facing)')
result = {'name': 'bad-request'}
@@ -48,7 +50,10 @@ def on_submission(self, data):
return {'name': 'submission-received', 'submission-id': id}
def on_termination(self, data):
- return {'name': 'submission-received', 'judge-aborted': self.judges.abort(data['submission-id'])}
+ return {
+ 'name': 'submission-received',
+ 'judge-aborted': self.judges.abort(data['submission-id']),
+ }
def on_disconnect_request(self, data):
judge_id = data['judge-id']
diff --git a/judge/bridge/echo_test_client.py b/judge/bridge/echo_test_client.py
index 8fec692aaf..762edfddcc 100644
--- a/judge/bridge/echo_test_client.py
+++ b/judge/bridge/echo_test_client.py
@@ -19,13 +19,14 @@ def zlibify(data):
def dezlibify(data, skip_head=True):
if skip_head:
- data = data[size_pack.size:]
+ data = data[size_pack.size :]
return zlib.decompress(data).decode('utf-8')
def main():
global host, port
import argparse
+
parser = argparse.ArgumentParser()
parser.add_argument('-l', '--host', default='localhost')
parser.add_argument('-p', '--port', default=9999, type=int)
@@ -58,8 +59,8 @@ def main():
result = b''
while len(result) < size_pack.size:
result += s4.recv(1024)
- size = size_pack.unpack(result[:size_pack.size])[0]
- result = result[size_pack.size:]
+ size = size_pack.unpack(result[: size_pack.size])[0]
+ result = result[size_pack.size :]
while len(result) < size:
result += s4.recv(1024)
print('Received', end=' ')
diff --git a/judge/bridge/echo_test_server.py b/judge/bridge/echo_test_server.py
index 59e21fa223..412688c8fa 100644
--- a/judge/bridge/echo_test_server.py
+++ b/judge/bridge/echo_test_server.py
@@ -11,7 +11,10 @@ def on_timeout(self):
def on_packet(self, data):
self.timeout = None
- print('Data from %s: %r' % (self.client_address, data[:30] if len(data) > 30 else data))
+ print(
+ 'Data from %s: %r'
+ % (self.client_address, data[:30] if len(data) > 30 else data)
+ )
self.send(data)
def on_disconnect(self):
diff --git a/judge/bridge/judge_handler.py b/judge/bridge/judge_handler.py
index 073de27262..b13b94e04f 100644
--- a/judge/bridge/judge_handler.py
+++ b/judge/bridge/judge_handler.py
@@ -13,14 +13,25 @@
from judge import event_poster as event
from judge.bridge.base_handler import ZlibPacketHandler, proxy_list
from judge.caching import finished_submission
-from judge.models import Judge, Language, LanguageLimit, Problem, RuntimeVersion, Submission, SubmissionTestCase
+from judge.models import (
+ Judge,
+ Language,
+ LanguageLimit,
+ Problem,
+ RuntimeVersion,
+ Submission,
+ SubmissionTestCase,
+)
logger = logging.getLogger('judge.bridge')
json_log = logging.getLogger('judge.json.bridge')
UPDATE_RATE_LIMIT = 5
UPDATE_RATE_TIME = 0.5
-SubmissionData = namedtuple('SubmissionData', 'time memory short_circuit pretests_only contest_no attempt_no user_id')
+SubmissionData = namedtuple(
+ 'SubmissionData',
+ 'time memory short_circuit pretests_only contest_no attempt_no user_id',
+)
def _ensure_connection():
@@ -81,16 +92,32 @@ def on_connect(self):
def on_disconnect(self):
self._stop_ping.set()
if self._working:
- logger.error('Judge %s disconnected while handling submission %s', self.name, self._working)
+ logger.error(
+ 'Judge %s disconnected while handling submission %s',
+ self.name,
+ self._working,
+ )
self.judges.remove(self)
if self.name is not None:
self._disconnected()
- logger.info('Judge disconnected from: %s with name %s', self.client_address, self.name)
+ logger.info(
+ 'Judge disconnected from: %s with name %s', self.client_address, self.name
+ )
- json_log.info(self._make_json_log(action='disconnect', info='judge disconnected'))
+ json_log.info(
+ self._make_json_log(action='disconnect', info='judge disconnected')
+ )
if self._working:
- Submission.objects.filter(id=self._working).update(status='IE', result='IE', error='')
- json_log.error(self._make_json_log(sub=self._working, action='close', info='IE due to shutdown on grading'))
+ Submission.objects.filter(id=self._working).update(
+ status='IE', result='IE', error=''
+ )
+ json_log.error(
+ self._make_json_log(
+ sub=self._working,
+ action='close',
+ info='IE due to shutdown on grading',
+ )
+ )
def _authenticate(self, id, key):
try:
@@ -100,11 +127,19 @@ def _authenticate(self, id, key):
if not hmac.compare_digest(judge.auth_key, key):
logger.warning('Judge authentication failure: %s', self.client_address)
- json_log.warning(self._make_json_log(action='auth', judge=id, info='judge failed authentication'))
+ json_log.warning(
+ self._make_json_log(
+ action='auth', judge=id, info='judge failed authentication'
+ )
+ )
return False
if judge.is_blocked:
- json_log.warning(self._make_json_log(action='auth', judge=id, info='judge authenticated but is blocked'))
+ json_log.warning(
+ self._make_json_log(
+ action='auth', judge=id, info='judge authenticated but is blocked'
+ )
+ )
return False
return True
@@ -124,15 +159,29 @@ def _connected(self):
versions = []
for lang in judge.runtimes.all():
versions += [
- RuntimeVersion(language=lang, name=name, version='.'.join(map(str, version)), priority=idx, judge=judge)
+ RuntimeVersion(
+ language=lang,
+ name=name,
+ version='.'.join(map(str, version)),
+ priority=idx,
+ judge=judge,
+ )
for idx, (name, version) in enumerate(self.executors[lang.key])
]
RuntimeVersion.objects.bulk_create(versions)
judge.last_ip = self.client_address[0]
judge.save()
- self.judge_address = '[%s]:%s' % (self.client_address[0], self.client_address[1])
- json_log.info(self._make_json_log(action='auth', info='judge successfully authenticated',
- executors=list(self.executors.keys())))
+ self.judge_address = '[%s]:%s' % (
+ self.client_address[0],
+ self.client_address[1],
+ )
+ json_log.info(
+ self._make_json_log(
+ action='auth',
+ info='judge successfully authenticated',
+ executors=list(self.executors.keys()),
+ )
+ )
def _disconnected(self):
Judge.objects.filter(id=self.judge.id).update(online=False)
@@ -140,10 +189,16 @@ def _disconnected(self):
def _update_ping(self):
try:
- Judge.objects.filter(name=self.name).update(ping=self.latency, load=self.load)
+ Judge.objects.filter(name=self.name).update(
+ ping=self.latency, load=self.load
+ )
except Exception as e:
# What can I do? I don't want to tie this to MySQL.
- if e.__class__.__name__ == 'OperationalError' and e.__module__ == '_mysql_exceptions' and e.args[0] == 2006:
+ if (
+ e.__class__.__name__ == 'OperationalError'
+ and e.__module__ == '_mysql_exceptions'
+ and e.args[0] == 2006
+ ):
db.connection.close()
def send(self, data):
@@ -172,8 +227,11 @@ def on_handshake(self, packet):
self._connected()
def can_judge(self, problem, executor, judge_id=None):
- return problem in self.problems and executor in self.executors and \
- ((not judge_id and not self.is_disabled) or self.name == judge_id)
+ return (
+ problem in self.problems
+ and executor in self.executors
+ and ((not judge_id and not self.is_disabled) or self.name == judge_id)
+ )
@property
def working(self):
@@ -183,25 +241,60 @@ def get_related_submission_data(self, submission):
_ensure_connection()
try:
- pid, time, memory, short_circuit, lid, is_pretested, sub_date, uid, part_virtual, part_id = (
- Submission.objects.filter(id=submission)
- .values_list('problem__id', 'problem__time_limit', 'problem__memory_limit',
- 'problem__short_circuit', 'language__id', 'is_pretested', 'date', 'user__id',
- 'contest__participation__virtual', 'contest__participation__id')).get()
+ (
+ pid,
+ time,
+ memory,
+ short_circuit,
+ lid,
+ is_pretested,
+ sub_date,
+ uid,
+ part_virtual,
+ part_id,
+ ) = (
+ Submission.objects.filter(id=submission).values_list(
+ 'problem__id',
+ 'problem__time_limit',
+ 'problem__memory_limit',
+ 'problem__short_circuit',
+ 'language__id',
+ 'is_pretested',
+ 'date',
+ 'user__id',
+ 'contest__participation__virtual',
+ 'contest__participation__id',
+ )
+ ).get()
except Submission.DoesNotExist:
logger.error('Submission vanished: %s', submission)
- json_log.error(self._make_json_log(
- sub=self._working, action='request',
- info='submission vanished when fetching info',
- ))
+ json_log.error(
+ self._make_json_log(
+ sub=self._working,
+ action='request',
+ info='submission vanished when fetching info',
+ )
+ )
return
- attempt_no = Submission.objects.filter(problem__id=pid, contest__participation__id=part_id, user__id=uid,
- date__lt=sub_date).exclude(status__in=('CE', 'IE')).count() + 1
+ attempt_no = (
+ Submission.objects.filter(
+ problem__id=pid,
+ contest__participation__id=part_id,
+ user__id=uid,
+ date__lt=sub_date,
+ )
+ .exclude(status__in=('CE', 'IE'))
+ .count()
+ + 1
+ )
try:
- time, memory = (LanguageLimit.objects.filter(problem__id=pid, language__id=lid)
- .values_list('time_limit', 'memory_limit').get())
+ time, memory = (
+ LanguageLimit.objects.filter(problem__id=pid, language__id=lid)
+ .values_list('time_limit', 'memory_limit')
+ .get()
+ )
except LanguageLimit.DoesNotExist:
pass
@@ -226,25 +319,29 @@ def submit(self, id, problem, language, source):
data = self.get_related_submission_data(id)
self._working = id
self._no_response_job = threading.Timer(20, self._kill_if_no_response)
- self.send({
- 'name': 'submission-request',
- 'submission-id': id,
- 'problem-id': problem,
- 'language': language,
- 'source': source,
- 'time-limit': data.time,
- 'memory-limit': data.memory,
- 'short-circuit': data.short_circuit,
- 'meta': {
- 'pretests-only': data.pretests_only,
- 'in-contest': data.contest_no,
- 'attempt-no': data.attempt_no,
- 'user': data.user_id,
- },
- })
+ self.send(
+ {
+ 'name': 'submission-request',
+ 'submission-id': id,
+ 'problem-id': problem,
+ 'language': language,
+ 'source': source,
+ 'time-limit': data.time,
+ 'memory-limit': data.memory,
+ 'short-circuit': data.short_circuit,
+ 'meta': {
+ 'pretests-only': data.pretests_only,
+ 'in-contest': data.contest_no,
+ 'attempt-no': data.attempt_no,
+ 'user': data.user_id,
+ },
+ }
+ )
def _kill_if_no_response(self):
- logger.error('Judge failed to acknowledge submission: %s: %s', self.name, self._working)
+ logger.error(
+ 'Judge failed to acknowledge submission: %s: %s', self.name, self._working
+ )
self.close()
def on_timeout(self):
@@ -261,18 +358,36 @@ def on_submission_processing(self, packet):
json_log.info(self._make_json_log(packet, action='processing'))
else:
logger.warning('Unknown submission: %s', id)
- json_log.error(self._make_json_log(packet, action='processing', info='unknown submission'))
+ json_log.error(
+ self._make_json_log(
+ packet, action='processing', info='unknown submission'
+ )
+ )
def on_submission_wrong_acknowledge(self, packet, expected, got):
- json_log.error(self._make_json_log(packet, action='processing', info='wrong-acknowledge', expected=expected))
- Submission.objects.filter(id=expected).update(status='IE', result='IE', error=None)
- Submission.objects.filter(id=got, status='QU').update(status='IE', result='IE', error=None)
+ json_log.error(
+ self._make_json_log(
+ packet, action='processing', info='wrong-acknowledge', expected=expected
+ )
+ )
+ Submission.objects.filter(id=expected).update(
+ status='IE', result='IE', error=None
+ )
+ Submission.objects.filter(id=got, status='QU').update(
+ status='IE', result='IE', error=None
+ )
def on_submission_acknowledged(self, packet):
if not packet.get('submission-id', None) == self._working:
- logger.error('Wrong acknowledgement: %s: %s, expected: %s', self.name, packet.get('submission-id', None),
- self._working)
- self.on_submission_wrong_acknowledge(packet, self._working, packet.get('submission-id', None))
+ logger.error(
+ 'Wrong acknowledgement: %s: %s, expected: %s',
+ self.name,
+ packet.get('submission-id', None),
+ self._working,
+ )
+ self.on_submission_wrong_acknowledge(
+ packet, self._working, packet.get('submission-id', None)
+ )
self.close()
logger.info('Submission acknowledged: %d', self._working)
if self._no_response_job:
@@ -307,7 +422,9 @@ def on_packet(self, data):
# not being malicious or simply malformed. THIS IS A SERVER!
def _packet_exception(self):
- json_log.exception(self._make_json_log(sub=self._working, info='packet processing exception'))
+ json_log.exception(
+ self._make_json_log(sub=self._working, info='packet processing exception')
+ )
def _submission_is_batch(self, id):
if not Submission.objects.filter(id=id).update(batch=True):
@@ -320,23 +437,40 @@ def on_supported_problems(self, packet):
if not self.working:
self.judges.update_problems(self)
- self.judge.problems.set(Problem.objects.filter(code__in=list(self.problems.keys())))
- json_log.info(self._make_json_log(action='update-problems', count=len(self.problems)))
+ self.judge.problems.set(
+ Problem.objects.filter(code__in=list(self.problems.keys()))
+ )
+ json_log.info(
+ self._make_json_log(action='update-problems', count=len(self.problems))
+ )
def on_grading_begin(self, packet):
logger.info('%s: Grading has begun on: %s', self.name, packet['submission-id'])
self.batch_id = None
if Submission.objects.filter(id=packet['submission-id']).update(
- status='G', is_pretested=packet['pretested'], current_testcase=1,
- batch=False, judged_date=timezone.now()):
- SubmissionTestCase.objects.filter(submission_id=packet['submission-id']).delete()
- event.post('sub_%s' % Submission.get_id_secret(packet['submission-id']), {'type': 'grading-begin'})
+ status='G',
+ is_pretested=packet['pretested'],
+ current_testcase=1,
+ batch=False,
+ judged_date=timezone.now(),
+ ):
+ SubmissionTestCase.objects.filter(
+ submission_id=packet['submission-id']
+ ).delete()
+ event.post(
+ 'sub_%s' % Submission.get_id_secret(packet['submission-id']),
+ {'type': 'grading-begin'},
+ )
self._post_update_submission(packet['submission-id'], 'grading-begin')
json_log.info(self._make_json_log(packet, action='grading-begin'))
else:
logger.warning('Unknown submission: %s', packet['submission-id'])
- json_log.error(self._make_json_log(packet, action='grading-begin', info='unknown submission'))
+ json_log.error(
+ self._make_json_log(
+ packet, action='grading-begin', info='unknown submission'
+ )
+ )
def on_grading_end(self, packet):
logger.info('%s: Grading has ended on: %s', self.name, packet['submission-id'])
@@ -347,7 +481,11 @@ def on_grading_end(self, packet):
submission = Submission.objects.get(id=packet['submission-id'])
except Submission.DoesNotExist:
logger.warning('Unknown submission: %s', packet['submission-id'])
- json_log.error(self._make_json_log(packet, action='grading-end', info='unknown submission'))
+ json_log.error(
+ self._make_json_log(
+ packet, action='grading-end', info='unknown submission'
+ )
+ )
return
time = 0
@@ -395,12 +533,22 @@ def on_grading_end(self, packet):
submission.result = status_codes[status]
submission.save()
- json_log.info(self._make_json_log(
- packet, action='grading-end', time=time, memory=memory,
- points=sub_points, total=problem.points, result=submission.result,
- case_points=points, case_total=total, user=submission.user_id,
- problem=problem.code, finish=True,
- ))
+ json_log.info(
+ self._make_json_log(
+ packet,
+ action='grading-end',
+ time=time,
+ memory=memory,
+ points=sub_points,
+ total=problem.points,
+ result=submission.result,
+ case_points=points,
+ case_total=total,
+ user=submission.user_id,
+ problem=problem.code,
+ finish=True,
+ )
+ )
if problem.is_public and not problem.is_organization_private:
submission.user._updating_stats_only = True
@@ -412,77 +560,158 @@ def on_grading_end(self, packet):
finished_submission(submission)
- event.post('sub_%s' % submission.id_secret, {
- 'type': 'grading-end',
- 'time': time,
- 'memory': memory,
- 'points': float(points),
- 'total': float(problem.points),
- 'result': submission.result,
- })
+ event.post(
+ 'sub_%s' % submission.id_secret,
+ {
+ 'type': 'grading-end',
+ 'time': time,
+ 'memory': memory,
+ 'points': float(points),
+ 'total': float(problem.points),
+ 'result': submission.result,
+ },
+ )
if hasattr(submission, 'contest'):
participation = submission.contest.participation
event.post('contest_%d' % participation.contest_id, {'type': 'update'})
self._post_update_submission(submission.id, 'grading-end', done=True)
def on_compile_error(self, packet):
- logger.info('%s: Submission failed to compile: %s', self.name, packet['submission-id'])
+ logger.info(
+ '%s: Submission failed to compile: %s', self.name, packet['submission-id']
+ )
self._free_self(packet)
- if Submission.objects.filter(id=packet['submission-id']).update(status='CE', result='CE', error=packet['log']):
- event.post('sub_%s' % Submission.get_id_secret(packet['submission-id']), {
- 'type': 'compile-error',
- 'log': packet['log'],
- })
- self._post_update_submission(packet['submission-id'], 'compile-error', done=True)
- json_log.info(self._make_json_log(packet, action='compile-error', log=packet['log'],
- finish=True, result='CE'))
+ if Submission.objects.filter(id=packet['submission-id']).update(
+ status='CE', result='CE', error=packet['log']
+ ):
+ event.post(
+ 'sub_%s' % Submission.get_id_secret(packet['submission-id']),
+ {
+ 'type': 'compile-error',
+ 'log': packet['log'],
+ },
+ )
+ self._post_update_submission(
+ packet['submission-id'], 'compile-error', done=True
+ )
+ json_log.info(
+ self._make_json_log(
+ packet,
+ action='compile-error',
+ log=packet['log'],
+ finish=True,
+ result='CE',
+ )
+ )
else:
logger.warning('Unknown submission: %s', packet['submission-id'])
- json_log.error(self._make_json_log(packet, action='compile-error', info='unknown submission',
- log=packet['log'], finish=True, result='CE'))
+ json_log.error(
+ self._make_json_log(
+ packet,
+ action='compile-error',
+ info='unknown submission',
+ log=packet['log'],
+ finish=True,
+ result='CE',
+ )
+ )
def on_compile_message(self, packet):
- logger.info('%s: Submission generated compiler messages: %s', self.name, packet['submission-id'])
+ logger.info(
+ '%s: Submission generated compiler messages: %s',
+ self.name,
+ packet['submission-id'],
+ )
- if Submission.objects.filter(id=packet['submission-id']).update(error=packet['log']):
- event.post('sub_%s' % Submission.get_id_secret(packet['submission-id']), {'type': 'compile-message'})
- json_log.info(self._make_json_log(packet, action='compile-message', log=packet['log']))
+ if Submission.objects.filter(id=packet['submission-id']).update(
+ error=packet['log']
+ ):
+ event.post(
+ 'sub_%s' % Submission.get_id_secret(packet['submission-id']),
+ {'type': 'compile-message'},
+ )
+ json_log.info(
+ self._make_json_log(packet, action='compile-message', log=packet['log'])
+ )
else:
logger.warning('Unknown submission: %s', packet['submission-id'])
- json_log.error(self._make_json_log(packet, action='compile-message', info='unknown submission',
- log=packet['log']))
+ json_log.error(
+ self._make_json_log(
+ packet,
+ action='compile-message',
+ info='unknown submission',
+ log=packet['log'],
+ )
+ )
def on_internal_error(self, packet):
try:
raise ValueError('\n\n' + packet['message'])
except ValueError:
- logger.exception('Judge %s failed while handling submission %s', self.name, packet['submission-id'])
+ logger.exception(
+ 'Judge %s failed while handling submission %s',
+ self.name,
+ packet['submission-id'],
+ )
self._free_self(packet)
id = packet['submission-id']
- if Submission.objects.filter(id=id).update(status='IE', result='IE', error=packet['message']):
- event.post('sub_%s' % Submission.get_id_secret(id), {'type': 'internal-error'})
+ if Submission.objects.filter(id=id).update(
+ status='IE', result='IE', error=packet['message']
+ ):
+ event.post(
+ 'sub_%s' % Submission.get_id_secret(id), {'type': 'internal-error'}
+ )
self._post_update_submission(id, 'internal-error', done=True)
- json_log.info(self._make_json_log(packet, action='internal-error', message=packet['message'],
- finish=True, result='IE'))
+ json_log.info(
+ self._make_json_log(
+ packet,
+ action='internal-error',
+ message=packet['message'],
+ finish=True,
+ result='IE',
+ )
+ )
else:
logger.warning('Unknown submission: %s', id)
- json_log.error(self._make_json_log(packet, action='internal-error', info='unknown submission',
- message=packet['message'], finish=True, result='IE'))
+ json_log.error(
+ self._make_json_log(
+ packet,
+ action='internal-error',
+ info='unknown submission',
+ message=packet['message'],
+ finish=True,
+ result='IE',
+ )
+ )
def on_submission_terminated(self, packet):
logger.info('%s: Submission aborted: %s', self.name, packet['submission-id'])
self._free_self(packet)
- if Submission.objects.filter(id=packet['submission-id']).update(status='AB', result='AB', points=0):
- event.post('sub_%s' % Submission.get_id_secret(packet['submission-id']), {'type': 'aborted'})
+ if Submission.objects.filter(id=packet['submission-id']).update(
+ status='AB', result='AB', points=0
+ ):
+ event.post(
+ 'sub_%s' % Submission.get_id_secret(packet['submission-id']),
+ {'type': 'aborted'},
+ )
self._post_update_submission(packet['submission-id'], 'aborted', done=True)
- json_log.info(self._make_json_log(packet, action='aborted', finish=True, result='AB'))
+ json_log.info(
+ self._make_json_log(packet, action='aborted', finish=True, result='AB')
+ )
else:
logger.warning('Unknown submission: %s', packet['submission-id'])
- json_log.error(self._make_json_log(packet, action='aborted', info='unknown submission',
- finish=True, result='AB'))
+ json_log.error(
+ self._make_json_log(
+ packet,
+ action='aborted',
+ info='unknown submission',
+ finish=True,
+ result='AB',
+ )
+ )
def on_batch_begin(self, packet):
logger.info('%s: Batch began on: %s', self.name, packet['submission-id'])
@@ -492,23 +721,42 @@ def on_batch_begin(self, packet):
self._submission_is_batch(packet['submission-id'])
self.batch_id += 1
- json_log.info(self._make_json_log(packet, action='batch-begin', batch=self.batch_id))
+ json_log.info(
+ self._make_json_log(packet, action='batch-begin', batch=self.batch_id)
+ )
def on_batch_end(self, packet):
self.in_batch = False
logger.info('%s: Batch ended on: %s', self.name, packet['submission-id'])
- json_log.info(self._make_json_log(packet, action='batch-end', batch=self.batch_id))
+ json_log.info(
+ self._make_json_log(packet, action='batch-end', batch=self.batch_id)
+ )
- def on_test_case(self, packet, max_feedback=SubmissionTestCase._meta.get_field('feedback').max_length):
- logger.info('%s: %d test case(s) completed on: %s', self.name, len(packet['cases']), packet['submission-id'])
+ def on_test_case(
+ self,
+ packet,
+ max_feedback=SubmissionTestCase._meta.get_field('feedback').max_length,
+ ):
+ logger.info(
+ '%s: %d test case(s) completed on: %s',
+ self.name,
+ len(packet['cases']),
+ packet['submission-id'],
+ )
id = packet['submission-id']
updates = packet['cases']
max_position = max(map(itemgetter('position'), updates))
- if not Submission.objects.filter(id=id).update(current_testcase=max_position + 1):
+ if not Submission.objects.filter(id=id).update(
+ current_testcase=max_position + 1
+ ):
logger.warning('Unknown submission: %s', id)
- json_log.error(self._make_json_log(packet, action='test-case', info='unknown submission'))
+ json_log.error(
+ self._make_json_log(
+ packet, action='test-case', info='unknown submission'
+ )
+ )
return
bulk_test_case_updates = []
@@ -541,15 +789,29 @@ def on_test_case(self, packet, max_feedback=SubmissionTestCase._meta.get_field('
test_case.output = result['output']
bulk_test_case_updates.append(test_case)
- json_log.info(self._make_json_log(
- packet, action='test-case', case=test_case.case, batch=test_case.batch,
- time=test_case.time, memory=test_case.memory, feedback=test_case.feedback,
- extended_feedback=test_case.extended_feedback, output=test_case.output,
- points=test_case.points, total=test_case.total, status=test_case.status,
- voluntary_context_switches=result.get('voluntary-context-switches', 0),
- involuntary_context_switches=result.get('involuntary-context-switches', 0),
- runtime_version=result.get('runtime-version', ''),
- ))
+ json_log.info(
+ self._make_json_log(
+ packet,
+ action='test-case',
+ case=test_case.case,
+ batch=test_case.batch,
+ time=test_case.time,
+ memory=test_case.memory,
+ feedback=test_case.feedback,
+ extended_feedback=test_case.extended_feedback,
+ output=test_case.output,
+ points=test_case.points,
+ total=test_case.total,
+ status=test_case.status,
+ voluntary_context_switches=result.get(
+ 'voluntary-context-switches', 0
+ ),
+ involuntary_context_switches=result.get(
+ 'involuntary-context-switches', 0
+ ),
+ runtime_version=result.get('runtime-version', ''),
+ )
+ )
do_post = True
@@ -566,17 +828,22 @@ def on_test_case(self, packet, max_feedback=SubmissionTestCase._meta.get_field('
self.update_counter[id] = (1, time.monotonic())
if do_post:
- event.post('sub_%s' % Submission.get_id_secret(id), {
- 'type': 'test-case',
- 'id': max_position,
- })
+ event.post(
+ 'sub_%s' % Submission.get_id_secret(id),
+ {
+ 'type': 'test-case',
+ 'id': max_position,
+ },
+ )
self._post_update_submission(id, state='test-case')
SubmissionTestCase.objects.bulk_create(bulk_test_case_updates)
def on_malformed(self, packet):
logger.error('%s: Malformed packet: %s', self.name, packet)
- json_log.exception(self._make_json_log(sub=self._working, info='malformed json packet'))
+ json_log.exception(
+ self._make_json_log(sub=self._working, info='malformed json packet')
+ )
def on_ping_response(self, packet):
end = time.time()
@@ -617,20 +884,34 @@ def _post_update_submission(self, id, state, done=False):
if self._submission_cache_id == id:
data = self._submission_cache
else:
- self._submission_cache = data = Submission.objects.filter(id=id).values(
- 'problem__is_public', 'contest_object_id',
- 'user_id', 'problem_id', 'status', 'language__key',
- ).get()
+ self._submission_cache = data = (
+ Submission.objects.filter(id=id)
+ .values(
+ 'problem__is_public',
+ 'contest_object_id',
+ 'user_id',
+ 'problem_id',
+ 'status',
+ 'language__key',
+ )
+ .get()
+ )
self._submission_cache_id = id
if data['problem__is_public']:
- event.post('submissions', {
- 'type': 'done-submission' if done else 'update-submission',
- 'state': state, 'id': id,
- 'contest': data['contest_object_id'],
- 'user': data['user_id'], 'problem': data['problem_id'],
- 'status': data['status'], 'language': data['language__key'],
- })
+ event.post(
+ 'submissions',
+ {
+ 'type': 'done-submission' if done else 'update-submission',
+ 'state': state,
+ 'id': id,
+ 'contest': data['contest_object_id'],
+ 'user': data['user_id'],
+ 'problem': data['problem_id'],
+ 'status': data['status'],
+ 'language': data['language__key'],
+ },
+ )
def on_cleanup(self):
db.connection.close()
diff --git a/judge/bridge/judge_list.py b/judge/bridge/judge_list.py
index b65d0ddb82..5d8cd9a2f7 100644
--- a/judge/bridge/judge_list.py
+++ b/judge/bridge/judge_list.py
@@ -20,7 +20,9 @@ class JudgeList(object):
def __init__(self):
self.queue = dllist()
- self.priority = [self.queue.append(PriorityMarker(i)) for i in range(self.priorities)]
+ self.priority = [
+ self.queue.append(PriorityMarker(i)) for i in range(self.priorities)
+ ]
self.judges = set()
self.node_map = {}
self.submission_map = {}
@@ -33,8 +35,15 @@ def _handle_free_judge(self, judge):
while node:
if isinstance(node.value, PriorityMarker):
priority = node.value.priority + 1
- elif priority >= REJUDGE_PRIORITY and self.count_not_disabled() > 1 and sum(
- not judge.working and not judge.is_disabled for judge in self.judges) <= 1:
+ elif (
+ priority >= REJUDGE_PRIORITY
+ and self.count_not_disabled() > 1
+ and sum(
+ not judge.working and not judge.is_disabled
+ for judge in self.judges
+ )
+ <= 1
+ ):
return
else:
id, problem, language, source, judge_id = node.value
@@ -43,10 +52,18 @@ def _handle_free_judge(self, judge):
try:
judge.submit(id, problem, language, source)
except Exception:
- logger.exception('Failed to dispatch %d (%s, %s) to %s', id, problem, language, judge.name)
+ logger.exception(
+ 'Failed to dispatch %d (%s, %s) to %s',
+ id,
+ problem,
+ language,
+ judge.name,
+ )
self.judges.remove(judge)
return
- logger.info('Dispatched queued submission %d: %s', id, judge.name)
+ logger.info(
+ 'Dispatched queued submission %d: %s', id, judge.name
+ )
self.queue.remove(node)
del self.node_map[id]
break
@@ -131,14 +148,30 @@ def judge(self, id, problem, language, source, judge_id, priority):
# idempotent.
return
- candidates = [judge for judge in self.judges if judge.can_judge(problem, language, judge_id)]
- available = [judge for judge in candidates if not judge.working and not judge.is_disabled]
+ candidates = [
+ judge
+ for judge in self.judges
+ if judge.can_judge(problem, language, judge_id)
+ ]
+ available = [
+ judge
+ for judge in candidates
+ if not judge.working and not judge.is_disabled
+ ]
if judge_id:
- logger.info('Specified judge %s is%savailable', judge_id, ' ' if available else ' not ')
+ logger.info(
+ 'Specified judge %s is%savailable',
+ judge_id,
+ ' ' if available else ' not ',
+ )
else:
logger.info('Free judges: %d', len(available))
- if len(candidates) > 1 and len(available) == 1 and priority >= REJUDGE_PRIORITY:
+ if (
+ len(candidates) > 1
+ and len(available) == 1
+ and priority >= REJUDGE_PRIORITY
+ ):
available = []
if available:
@@ -149,7 +182,13 @@ def judge(self, id, problem, language, source, judge_id, priority):
try:
judge.submit(id, problem, language, source)
except Exception:
- logger.exception('Failed to dispatch %d (%s, %s) to %s', id, problem, language, judge.name)
+ logger.exception(
+ 'Failed to dispatch %d (%s, %s) to %s',
+ id,
+ problem,
+ language,
+ judge.name,
+ )
self.judges.discard(judge)
return self.judge(id, problem, language, source, judge_id, priority)
else:
diff --git a/judge/bridge/server.py b/judge/bridge/server.py
index cc83f84d13..4e67310773 100644
--- a/judge/bridge/server.py
+++ b/judge/bridge/server.py
@@ -12,7 +12,9 @@ def __init__(self, addresses, handler):
self._shutdown = threading.Event()
def serve_forever(self):
- threads = [threading.Thread(target=server.serve_forever) for server in self.servers]
+ threads = [
+ threading.Thread(target=server.serve_forever) for server in self.servers
+ ]
for thread in threads:
thread.daemon = True
thread.start()
diff --git a/judge/comments.py b/judge/comments.py
index 21481394ff..83fc89ace2 100644
--- a/judge/comments.py
+++ b/judge/comments.py
@@ -7,7 +7,12 @@
from django.db.models.expressions import F, Value
from django.db.models.functions import Coalesce
from django.forms import ModelForm
-from django.http import HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound, HttpResponseRedirect
+from django.http import (
+ HttpResponseBadRequest,
+ HttpResponseForbidden,
+ HttpResponseNotFound,
+ HttpResponseRedirect,
+)
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
@@ -32,8 +37,11 @@ class Meta:
}
if HeavyPreviewPageDownWidget is not None:
- widgets['body'] = HeavyPreviewPageDownWidget(preview=reverse_lazy('comment_preview'),
- preview_timeout=1000, hide_preview_button=True)
+ widgets['body'] = HeavyPreviewPageDownWidget(
+ preview=reverse_lazy('comment_preview'),
+ preview_timeout=1000,
+ hide_preview_button=True,
+ )
def __init__(self, request, *args, **kwargs):
self.request = request
@@ -46,7 +54,11 @@ def clean(self):
if profile.mute:
raise ValidationError(_('Your part is silent, little toad.'))
elif not self.request.user.is_staff and not profile.has_any_solves:
- raise ValidationError(_('You must solve at least one problem before your voice can be heard.'))
+ raise ValidationError(
+ _(
+ 'You must solve at least one problem before your voice can be heard.'
+ )
+ )
return super(CommentForm, self).clean()
@@ -59,8 +71,9 @@ def get_comment_page(self):
return self.comment_page
def is_comment_locked(self):
- return (CommentLock.objects.filter(page=self.get_comment_page()).exists() and
- not self.request.user.has_perm('judge.override_comment_lock'))
+ return CommentLock.objects.filter(
+ page=self.get_comment_page()
+ ).exists() and not self.request.user.has_perm('judge.override_comment_lock')
@method_decorator(login_required)
def post(self, request, *args, **kwargs):
@@ -82,8 +95,11 @@ def post(self, request, *args, **kwargs):
parent_comment = Comment.objects.get(hidden=False, id=parent, page=page)
except Comment.DoesNotExist:
return HttpResponseNotFound()
- if not (self.request.user.has_perm('judge.change_comment') or
- parent_comment.time > timezone.now() - settings.DMOJ_COMMENT_REPLY_TIMEFRAME):
+ if not (
+ self.request.user.has_perm('judge.change_comment')
+ or parent_comment.time
+ > timezone.now() - settings.DMOJ_COMMENT_REPLY_TIMEFRAME
+ ):
return HttpResponseForbidden()
form = CommentForm(request, request.POST)
@@ -91,7 +107,9 @@ def post(self, request, *args, **kwargs):
comment = form.save(commit=False)
comment.author = request.profile
comment.page = page
- with LockModel(write=(Comment, Revision, Version), read=(ContentType,)), revisions.create_revision():
+ with LockModel(
+ write=(Comment, Revision, Version), read=(ContentType,)
+ ), revisions.create_revision():
revisions.set_user(request.user)
revisions.set_comment(_('Posted comment'))
comment.save()
@@ -102,10 +120,14 @@ def post(self, request, *args, **kwargs):
def get(self, request, *args, **kwargs):
self.object = self.get_object()
- return self.render_to_response(self.get_context_data(
- object=self.object,
- comment_form=CommentForm(request, initial={'page': self.get_comment_page(), 'parent': None}),
- ))
+ return self.render_to_response(
+ self.get_context_data(
+ object=self.object,
+ comment_form=CommentForm(
+ request, initial={'page': self.get_comment_page(), 'parent': None}
+ ),
+ )
+ )
def get_context_data(self, **kwargs):
context = super(CommentedDetailView, self).get_context_data(**kwargs)
@@ -117,9 +139,13 @@ def get_context_data(self, **kwargs):
if self.request.user.is_authenticated:
profile = self.request.profile
queryset = queryset.annotate(
- my_vote=FilteredRelation('votes', condition=Q(votes__voter_id=profile.id)),
+ my_vote=FilteredRelation(
+ 'votes', condition=Q(votes__voter_id=profile.id)
+ ),
).annotate(vote_score=Coalesce(F('my_vote__score'), Value(0)))
- context['is_new_user'] = not self.request.user.is_staff and not profile.has_any_solves
+ context['is_new_user'] = (
+ not self.request.user.is_staff and not profile.has_any_solves
+ )
context['comment_list'] = queryset
context['vote_hide_threshold'] = settings.DMOJ_COMMENT_VOTE_HIDE_THRESHOLD
context['reply_cutoff'] = timezone.now() - settings.DMOJ_COMMENT_REPLY_TIMEFRAME
diff --git a/judge/contest_format/atcoder.py b/judge/contest_format/atcoder.py
index 9585eee1bf..8bd7d1de9d 100644
--- a/judge/contest_format/atcoder.py
+++ b/judge/contest_format/atcoder.py
@@ -29,7 +29,9 @@ def validate(cls, config):
return
if not isinstance(config, dict):
- raise ValidationError('AtCoder-styled contest expects no config or dict as config')
+ raise ValidationError(
+ 'AtCoder-styled contest expects no config or dict as config'
+ )
for key, value in config.items():
if key not in cls.config_defaults:
@@ -37,7 +39,9 @@ def validate(cls, config):
if not isinstance(value, type(cls.config_defaults[key])):
raise ValidationError('invalid type for config key "%s"' % key)
if not cls.config_validators[key](value):
- raise ValidationError('invalid value "%s" for config key "%s"' % (value, key))
+ raise ValidationError(
+ 'invalid value "%s" for config key "%s"' % (value, key)
+ )
def __init__(self, contest, config):
self.config = self.config_defaults.copy()
@@ -51,7 +55,8 @@ def update_participation(self, participation):
format_data = {}
with connection.cursor() as cursor:
- cursor.execute("""
+ cursor.execute(
+ """
SELECT MAX(cs.points) as `score`, (
SELECT MIN(csub.date)
FROM judge_contestsubmission ccs LEFT OUTER JOIN
@@ -62,7 +67,9 @@ def update_participation(self, participation):
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
judge_submission sub ON (sub.id = cs.submission_id)
GROUP BY cp.id
- """, (participation.id, participation.id))
+ """,
+ (participation.id, participation.id),
+ )
for score, time, prob in cursor.fetchall():
time = from_database_time(time)
@@ -71,9 +78,13 @@ def update_participation(self, participation):
# Compute penalty
if self.config['penalty']:
# An IE can have a submission result of `None`
- subs = participation.submissions.exclude(submission__result__isnull=True) \
- .exclude(submission__result__in=['IE', 'CE']) \
- .filter(problem_id=prob)
+ subs = (
+ participation.submissions.exclude(
+ submission__result__isnull=True
+ )
+ .exclude(submission__result__in=['IE', 'CE'])
+ .filter(problem_id=prob)
+ )
if score:
prev = subs.filter(submission__date__lte=time).count() - 1
penalty += prev * self.config['penalty'] * 60
@@ -98,14 +109,35 @@ def update_participation(self, participation):
def display_user_problem(self, participation, contest_problem):
format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data:
- penalty = format_html(' ({penalty}) ',
- penalty=floatformat(format_data['penalty'])) if format_data['penalty'] else ''
+ penalty = (
+ format_html(
+ ' ({penalty}) ',
+ penalty=floatformat(format_data['penalty']),
+ )
+ if format_data['penalty']
+ else ''
+ )
return format_html(
'{points}{penalty}{time}
',
- state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') +
- self.best_solution_state(format_data['points'], contest_problem.points)),
- url=reverse('contest_user_submissions',
- args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]),
+ state=(
+ (
+ 'pretest-'
+ if self.contest.run_pretests_only
+ and contest_problem.is_pretested
+ else ''
+ )
+ + self.best_solution_state(
+ format_data['points'], contest_problem.points
+ )
+ ),
+ url=reverse(
+ 'contest_user_submissions',
+ args=[
+ self.contest.key,
+ participation.user.user.username,
+ contest_problem.problem.code,
+ ],
+ ),
points=floatformat(format_data['points']),
penalty=penalty,
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'),
diff --git a/judge/contest_format/default.py b/judge/contest_format/default.py
index 1cf3e9861c..1a14af0994 100644
--- a/judge/contest_format/default.py
+++ b/judge/contest_format/default.py
@@ -20,7 +20,9 @@ class DefaultContestFormat(BaseContestFormat):
@classmethod
def validate(cls, config):
if config is not None and (not isinstance(config, dict) or config):
- raise ValidationError('default contest expects no config or empty dict as config')
+ raise ValidationError(
+ 'default contest expects no config or empty dict as config'
+ )
def __init__(self, contest, config):
super(DefaultContestFormat, self).__init__(contest, config)
@@ -31,12 +33,16 @@ def update_participation(self, participation):
format_data = {}
for result in participation.submissions.values('problem_id').annotate(
- time=Max('submission__date'), points=Max('points'),
+ time=Max('submission__date'),
+ points=Max('points'),
):
dt = (result['time'] - participation.start).total_seconds()
if result['points']:
cumtime += dt
- format_data[str(result['problem_id'])] = {'time': dt, 'points': result['points']}
+ format_data[str(result['problem_id'])] = {
+ 'time': dt,
+ 'points': result['points'],
+ }
points += result['points']
participation.cumtime = max(cumtime, 0)
@@ -50,10 +56,25 @@ def display_user_problem(self, participation, contest_problem):
if format_data:
return format_html(
'{points}{time}
',
- state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') +
- self.best_solution_state(format_data['points'], contest_problem.points)),
- url=reverse('contest_user_submissions',
- args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]),
+ state=(
+ (
+ 'pretest-'
+ if self.contest.run_pretests_only
+ and contest_problem.is_pretested
+ else ''
+ )
+ + self.best_solution_state(
+ format_data['points'], contest_problem.points
+ )
+ ),
+ url=reverse(
+ 'contest_user_submissions',
+ args=[
+ self.contest.key,
+ participation.user.user.username,
+ contest_problem.problem.code,
+ ],
+ ),
points=floatformat(format_data['points']),
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'),
)
@@ -63,18 +84,25 @@ def display_user_problem(self, participation, contest_problem):
def display_participation_result(self, participation):
return format_html(
'{points}{cumtime}
',
- url=reverse('contest_all_user_submissions',
- args=[self.contest.key, participation.user.user.username]),
+ url=reverse(
+ 'contest_all_user_submissions',
+ args=[self.contest.key, participation.user.user.username],
+ ),
points=floatformat(participation.score, -self.contest.points_precision),
cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday'),
)
def get_problem_breakdown(self, participation, contest_problems):
- return [(participation.format_data or {}).get(str(contest_problem.id)) for contest_problem in contest_problems]
+ return [
+ (participation.format_data or {}).get(str(contest_problem.id))
+ for contest_problem in contest_problems
+ ]
def get_label_for_problem(self, index):
return str(index + 1)
def get_short_form_display(self):
yield _('The maximum score submission for each problem will be used.')
- yield _('Ties will be broken by the sum of the last submission time on problems with a non-zero score.')
+ yield _(
+ 'Ties will be broken by the sum of the last submission time on problems with a non-zero score.'
+ )
diff --git a/judge/contest_format/ecoo.py b/judge/contest_format/ecoo.py
index 93c245c9c1..b7139b3ec9 100644
--- a/judge/contest_format/ecoo.py
+++ b/judge/contest_format/ecoo.py
@@ -17,7 +17,11 @@
class ECOOContestFormat(DefaultContestFormat):
name = gettext_lazy('ECOO')
config_defaults = {'cumtime': False, 'first_ac_bonus': 10, 'time_bonus': 5}
- config_validators = {'cumtime': lambda x: True, 'first_ac_bonus': lambda x: x >= 0, 'time_bonus': lambda x: x >= 0}
+ config_validators = {
+ 'cumtime': lambda x: True,
+ 'first_ac_bonus': lambda x: x >= 0,
+ 'time_bonus': lambda x: x >= 0,
+ }
"""
cumtime: Specify True if cumulative time is to be used in breaking ties. Defaults to False.
first_ac_bonus: The number of points to award if a solution gets AC on its first non-IE/CE run. Defaults to 10.
@@ -31,7 +35,9 @@ def validate(cls, config):
return
if not isinstance(config, dict):
- raise ValidationError('ECOO-styled contest expects no config or dict as config')
+ raise ValidationError(
+ 'ECOO-styled contest expects no config or dict as config'
+ )
for key, value in config.items():
if key not in cls.config_defaults:
@@ -39,7 +45,9 @@ def validate(cls, config):
if not isinstance(value, type(cls.config_defaults[key])):
raise ValidationError('invalid type for config key "%s"' % key)
if not cls.config_validators[key](value):
- raise ValidationError('invalid value "%s" for config key "%s"' % (value, key))
+ raise ValidationError(
+ 'invalid value "%s" for config key "%s"' % (value, key)
+ )
def __init__(self, contest, config):
self.config = self.config_defaults.copy()
@@ -51,18 +59,19 @@ def update_participation(self, participation):
score = 0
format_data = {}
- submissions = participation.submissions.exclude(submission__result__in=('IE', 'CE'))
+ submissions = participation.submissions.exclude(
+ submission__result__in=('IE', 'CE')
+ )
submission_counts = {
- data['problem_id']: data['count'] for data in submissions.values('problem_id').annotate(count=Count('id'))
+ data['problem_id']: data['count']
+ for data in submissions.values('problem_id').annotate(count=Count('id'))
}
queryset = (
- submissions
- .values('problem_id')
+ submissions.values('problem_id')
.filter(
submission__date=Subquery(
- submissions
- .filter(problem_id=OuterRef('problem_id'))
+ submissions.filter(problem_id=OuterRef('problem_id'))
.order_by('-submission__date')
.values('submission__date')[:1],
),
@@ -83,9 +92,17 @@ def update_participation(self, participation):
bonus += self.config['first_ac_bonus']
# Time bonus
if self.config['time_bonus']:
- bonus += (participation.end_time - date).total_seconds() // 60 // self.config['time_bonus']
-
- format_data[str(problem_id)] = {'time': dt, 'points': points, 'bonus': bonus}
+ bonus += (
+ (participation.end_time - date).total_seconds()
+ // 60
+ // self.config['time_bonus']
+ )
+
+ format_data[str(problem_id)] = {
+ 'time': dt,
+ 'points': points,
+ 'bonus': bonus,
+ }
for data in format_data.values():
if self.config['cumtime']:
@@ -101,15 +118,35 @@ def update_participation(self, participation):
def display_user_problem(self, participation, contest_problem):
format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data:
- bonus = format_html(' +{bonus} ',
- bonus=floatformat(format_data['bonus'])) if format_data['bonus'] else ''
+ bonus = (
+ format_html(
+ ' +{bonus} ', bonus=floatformat(format_data['bonus'])
+ )
+ if format_data['bonus']
+ else ''
+ )
return format_html(
'{points}{bonus}{time}
',
- state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') +
- self.best_solution_state(format_data['points'], contest_problem.points)),
- url=reverse('contest_user_submissions',
- args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]),
+ state=(
+ (
+ 'pretest-'
+ if self.contest.run_pretests_only
+ and contest_problem.is_pretested
+ else ''
+ )
+ + self.best_solution_state(
+ format_data['points'], contest_problem.points
+ )
+ ),
+ url=reverse(
+ 'contest_user_submissions',
+ args=[
+ self.contest.key,
+ participation.user.user.username,
+ contest_problem.problem.code,
+ ],
+ ),
points=floatformat(format_data['points']),
bonus=bonus,
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'),
@@ -120,14 +157,20 @@ def display_user_problem(self, participation, contest_problem):
def display_participation_result(self, participation):
return format_html(
'{points}{cumtime}
',
- url=reverse('contest_all_user_submissions',
- args=[self.contest.key, participation.user.user.username]),
+ url=reverse(
+ 'contest_all_user_submissions',
+ args=[self.contest.key, participation.user.user.username],
+ ),
points=floatformat(participation.score, -self.contest.points_precision),
- cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday') if self.config['cumtime'] else '',
+ cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday')
+ if self.config['cumtime']
+ else '',
)
def get_short_form_display(self):
- yield _('The score on your **last** non-CE submission for each problem will be used.')
+ yield _(
+ 'The score on your **last** non-CE submission for each problem will be used.'
+ )
first_ac_bonus = self.config['first_ac_bonus']
if first_ac_bonus:
@@ -144,6 +187,8 @@ def get_short_form_display(self):
) % time_bonus
if self.config['cumtime']:
- yield _('Ties will be broken by the sum of the last submission time on **all** problems.')
+ yield _(
+ 'Ties will be broken by the sum of the last submission time on **all** problems.'
+ )
else:
yield _('Ties by score will **not** be broken.')
diff --git a/judge/contest_format/icpc.py b/judge/contest_format/icpc.py
index 13dfe2ed7c..c470c98e86 100644
--- a/judge/contest_format/icpc.py
+++ b/judge/contest_format/icpc.py
@@ -29,7 +29,9 @@ def validate(cls, config):
return
if not isinstance(config, dict):
- raise ValidationError('ICPC-styled contest expects no config or dict as config')
+ raise ValidationError(
+ 'ICPC-styled contest expects no config or dict as config'
+ )
for key, value in config.items():
if key not in cls.config_defaults:
@@ -37,7 +39,9 @@ def validate(cls, config):
if not isinstance(value, type(cls.config_defaults[key])):
raise ValidationError('invalid type for config key "%s"' % key)
if not cls.config_validators[key](value):
- raise ValidationError('invalid value "%s" for config key "%s"' % (value, key))
+ raise ValidationError(
+ 'invalid value "%s" for config key "%s"' % (value, key)
+ )
def __init__(self, contest, config):
self.config = self.config_defaults.copy()
@@ -52,7 +56,8 @@ def update_participation(self, participation):
format_data = {}
with connection.cursor() as cursor:
- cursor.execute("""
+ cursor.execute(
+ """
SELECT MAX(cs.points) as `points`, (
SELECT MIN(csub.date)
FROM judge_contestsubmission ccs LEFT OUTER JOIN
@@ -63,7 +68,9 @@ def update_participation(self, participation):
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
judge_submission sub ON (sub.id = cs.submission_id)
GROUP BY cp.id
- """, (participation.id, participation.id))
+ """,
+ (participation.id, participation.id),
+ )
for points, time, prob in cursor.fetchall():
time = from_database_time(time)
@@ -72,9 +79,13 @@ def update_participation(self, participation):
# Compute penalty
if self.config['penalty']:
# An IE can have a submission result of `None`
- subs = participation.submissions.exclude(submission__result__isnull=True) \
- .exclude(submission__result__in=['IE', 'CE']) \
- .filter(problem_id=prob)
+ subs = (
+ participation.submissions.exclude(
+ submission__result__isnull=True
+ )
+ .exclude(submission__result__in=['IE', 'CE'])
+ .filter(problem_id=prob)
+ )
if points:
prev = subs.filter(submission__date__lte=time).count() - 1
penalty += prev * self.config['penalty'] * 60
@@ -100,14 +111,35 @@ def update_participation(self, participation):
def display_user_problem(self, participation, contest_problem):
format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data:
- penalty = format_html(' ({penalty}) ',
- penalty=floatformat(format_data['penalty'])) if format_data['penalty'] else ''
+ penalty = (
+ format_html(
+ ' ({penalty}) ',
+ penalty=floatformat(format_data['penalty']),
+ )
+ if format_data['penalty']
+ else ''
+ )
return format_html(
'{points}{penalty}{time}
',
- state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') +
- self.best_solution_state(format_data['points'], contest_problem.points)),
- url=reverse('contest_user_submissions',
- args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]),
+ state=(
+ (
+ 'pretest-'
+ if self.contest.run_pretests_only
+ and contest_problem.is_pretested
+ else ''
+ )
+ + self.best_solution_state(
+ format_data['points'], contest_problem.points
+ )
+ ),
+ url=reverse(
+ 'contest_user_submissions',
+ args=[
+ self.contest.key,
+ participation.user.user.username,
+ contest_problem.problem.code,
+ ],
+ ),
points=floatformat(format_data['points']),
penalty=penalty,
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'),
@@ -134,5 +166,7 @@ def get_short_form_display(self):
penalty,
) % penalty
- yield _('Ties will be broken by the sum of the last score altering submission time on problems with a non-zero '
- 'score, followed by the time of the last score altering submission.')
+ yield _(
+ 'Ties will be broken by the sum of the last score altering submission time on problems with a non-zero '
+ 'score, followed by the time of the last score altering submission.'
+ )
diff --git a/judge/contest_format/ioi.py b/judge/contest_format/ioi.py
index 9cb75fa6fe..ba74fc8147 100644
--- a/judge/contest_format/ioi.py
+++ b/judge/contest_format/ioi.py
@@ -20,7 +20,8 @@ def update_participation(self, participation):
format_data = {}
with connection.cursor() as cursor:
- cursor.execute("""
+ cursor.execute(
+ """
SELECT q.prob,
MIN(q.date) as `date`,
q.batch_points
@@ -64,7 +65,9 @@ def update_participation(self, participation):
ON p.prob = q.prob AND (p.batch = q.batch OR p.batch is NULL AND q.batch is NULL)
WHERE p.max_batch_points = q.batch_points
GROUP BY q.prob, q.batch
- """, (participation.id, participation.id))
+ """,
+ (participation.id, participation.id),
+ )
for problem_id, time, subtask_points in cursor.fetchall():
problem_id = str(problem_id)
@@ -77,7 +80,9 @@ def update_participation(self, participation):
if format_data.get(problem_id) is None:
format_data[problem_id] = {'points': 0, 'time': 0}
format_data[problem_id]['points'] += subtask_points
- format_data[problem_id]['time'] = max(dt, format_data[problem_id]['time'])
+ format_data[problem_id]['time'] = max(
+ dt, format_data[problem_id]['time']
+ )
for problem_data in format_data.values():
penalty = problem_data['time']
@@ -96,7 +101,9 @@ def get_short_form_display(self):
yield _('The maximum score for each problem batch will be used.')
if self.config['cumtime']:
- yield _('Ties will be broken by the sum of the last score altering submission time on problems with a '
- 'non-zero score.')
+ yield _(
+ 'Ties will be broken by the sum of the last score altering submission time on problems with a '
+ 'non-zero score.'
+ )
else:
yield _('Ties by score will **not** be broken.')
diff --git a/judge/contest_format/legacy_ioi.py b/judge/contest_format/legacy_ioi.py
index 0b80b8c2d8..d955ccf7e9 100644
--- a/judge/contest_format/legacy_ioi.py
+++ b/judge/contest_format/legacy_ioi.py
@@ -27,7 +27,9 @@ def validate(cls, config):
return
if not isinstance(config, dict):
- raise ValidationError('IOI-styled contest expects no config or dict as config')
+ raise ValidationError(
+ 'IOI-styled contest expects no config or dict as config'
+ )
for key, value in config.items():
if key not in cls.config_defaults:
@@ -45,12 +47,18 @@ def update_participation(self, participation):
score = 0
format_data = {}
- queryset = (participation.submissions.values('problem_id')
- .filter(points=Subquery(
- participation.submissions.filter(problem_id=OuterRef('problem_id'))
- .order_by('-points').values('points')[:1]))
- .annotate(time=Min('submission__date'))
- .values_list('problem_id', 'time', 'points'))
+ queryset = (
+ participation.submissions.values('problem_id')
+ .filter(
+ points=Subquery(
+ participation.submissions.filter(problem_id=OuterRef('problem_id'))
+ .order_by('-points')
+ .values('points')[:1]
+ )
+ )
+ .annotate(time=Min('submission__date'))
+ .values_list('problem_id', 'time', 'points')
+ )
for problem_id, time, points in queryset:
if self.config['cumtime']:
@@ -74,12 +82,29 @@ def display_user_problem(self, participation, contest_problem):
if format_data:
return format_html(
'{points}{time}
',
- state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') +
- self.best_solution_state(format_data['points'], contest_problem.points)),
- url=reverse('contest_user_submissions',
- args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]),
+ state=(
+ (
+ 'pretest-'
+ if self.contest.run_pretests_only
+ and contest_problem.is_pretested
+ else ''
+ )
+ + self.best_solution_state(
+ format_data['points'], contest_problem.points
+ )
+ ),
+ url=reverse(
+ 'contest_user_submissions',
+ args=[
+ self.contest.key,
+ participation.user.user.username,
+ contest_problem.problem.code,
+ ],
+ ),
points=floatformat(format_data['points']),
- time=nice_repr(timedelta(seconds=format_data['time']), 'noday') if self.config['cumtime'] else '',
+ time=nice_repr(timedelta(seconds=format_data['time']), 'noday')
+ if self.config['cumtime']
+ else '',
)
else:
return mark_safe(' ')
@@ -87,17 +112,23 @@ def display_user_problem(self, participation, contest_problem):
def display_participation_result(self, participation):
return format_html(
'{points}{cumtime}
',
- url=reverse('contest_all_user_submissions',
- args=[self.contest.key, participation.user.user.username]),
+ url=reverse(
+ 'contest_all_user_submissions',
+ args=[self.contest.key, participation.user.user.username],
+ ),
points=floatformat(participation.score, -self.contest.points_precision),
- cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday') if self.config['cumtime'] else '',
+ cumtime=nice_repr(timedelta(seconds=participation.cumtime), 'noday')
+ if self.config['cumtime']
+ else '',
)
def get_short_form_display(self):
yield _('The maximum score submission for each problem will be used.')
if self.config['cumtime']:
- yield _('Ties will be broken by the sum of the last score altering submission time on problems with a '
- 'non-zero score.')
+ yield _(
+ 'Ties will be broken by the sum of the last score altering submission time on problems with a '
+ 'non-zero score.'
+ )
else:
yield _('Ties by score will **not** be broken.')
diff --git a/judge/dblock.py b/judge/dblock.py
index d4d518424c..c71ead228e 100644
--- a/judge/dblock.py
+++ b/judge/dblock.py
@@ -5,10 +5,12 @@
class LockModel(object):
def __init__(self, write, read=()):
- self.tables = ', '.join(chain(
- ('`%s` WRITE' % model._meta.db_table for model in write),
- ('`%s` READ' % model._meta.db_table for model in read),
- ))
+ self.tables = ', '.join(
+ chain(
+ ('`%s` WRITE' % model._meta.db_table for model in write),
+ ('`%s` READ' % model._meta.db_table for model in read),
+ )
+ )
self.cursor = connection.cursor()
def __enter__(self):
diff --git a/judge/event_poster.py b/judge/event_poster.py
index 29100bd993..d2e7c67dc4 100644
--- a/judge/event_poster.py
+++ b/judge/event_poster.py
@@ -10,9 +10,12 @@ def post(channel, message):
def last():
return 0
+
elif hasattr(settings, 'EVENT_DAEMON_AMQP'):
from .event_poster_amqp import last, post
+
real = True
else:
from .event_poster_ws import last, post
+
real = True
diff --git a/judge/event_poster_amqp.py b/judge/event_poster_amqp.py
index 74f6331e11..4d492112ec 100644
--- a/judge/event_poster_amqp.py
+++ b/judge/event_poster_amqp.py
@@ -15,14 +15,19 @@ def __init__(self):
self._exchange = settings.EVENT_DAEMON_AMQP_EXCHANGE
def _connect(self):
- self._conn = pika.BlockingConnection(pika.URLParameters(settings.EVENT_DAEMON_AMQP))
+ self._conn = pika.BlockingConnection(
+ pika.URLParameters(settings.EVENT_DAEMON_AMQP)
+ )
self._chan = self._conn.channel()
def post(self, channel, message, tries=0):
try:
id = int(time() * 1000000)
- self._chan.basic_publish(self._exchange, '',
- json.dumps({'id': id, 'channel': channel, 'message': message}))
+ self._chan.basic_publish(
+ self._exchange,
+ '',
+ json.dumps({'id': id, 'channel': channel, 'message': message}),
+ )
return id
except AMQPError:
if tries > 10:
diff --git a/judge/event_poster_ws.py b/judge/event_poster_ws.py
index fba4052631..f7035c8209 100644
--- a/judge/event_poster_ws.py
+++ b/judge/event_poster_ws.py
@@ -20,14 +20,18 @@ def __init__(self):
def _connect(self):
self._conn = create_connection(settings.EVENT_DAEMON_POST)
if settings.EVENT_DAEMON_KEY is not None:
- self._conn.send(json.dumps({'command': 'auth', 'key': settings.EVENT_DAEMON_KEY}))
+ self._conn.send(
+ json.dumps({'command': 'auth', 'key': settings.EVENT_DAEMON_KEY})
+ )
resp = json.loads(self._conn.recv())
if resp['status'] == 'error':
raise EventPostingError(resp['code'])
def post(self, channel, message, tries=0):
try:
- self._conn.send(json.dumps({'command': 'post', 'channel': channel, 'message': message}))
+ self._conn.send(
+ json.dumps({'command': 'post', 'channel': channel, 'message': message})
+ )
resp = json.loads(self._conn.recv())
if resp['status'] == 'error':
raise EventPostingError(resp['code'])
diff --git a/judge/feed.py b/judge/feed.py
index 4e7cc3c641..fd865b6d6f 100644
--- a/judge/feed.py
+++ b/judge/feed.py
@@ -12,7 +12,9 @@
class ProblemFeed(Feed):
title = 'Recently Added %s Problems' % settings.SITE_NAME
link = '/'
- description = 'The latest problems added on the %s website' % settings.SITE_LONG_NAME
+ description = (
+ 'The latest problems added on the %s website' % settings.SITE_LONG_NAME
+ )
def items(self):
return Problem.get_public_problems().order_by('-date', '-id')[:25]
@@ -75,7 +77,9 @@ class BlogFeed(Feed):
description = 'The latest blog posts from the %s' % settings.SITE_LONG_NAME
def items(self):
- return BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now()).order_by('-sticky', '-publish_on')
+ return BlogPost.objects.filter(
+ visible=True, publish_on__lte=timezone.now()
+ ).order_by('-sticky', '-publish_on')
def item_title(self, post):
return post.title
diff --git a/judge/forms.py b/judge/forms.py
index 87003e56f2..7fd078a5bf 100644
--- a/judge/forms.py
+++ b/judge/forms.py
@@ -10,17 +10,36 @@
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db.models import Q
-from django.forms import BooleanField, CharField, ChoiceField, Form, ModelForm, MultipleChoiceField
+from django.forms import (
+ BooleanField,
+ CharField,
+ ChoiceField,
+ Form,
+ ModelForm,
+ MultipleChoiceField,
+)
from django.urls import reverse_lazy
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _, ngettext_lazy
from django_ace import AceWidget
-from judge.models import Contest, Language, Organization, Problem, ProblemPointsVote, Profile, Submission, \
- WebAuthnCredential
+from judge.models import (
+ Contest,
+ Language,
+ Organization,
+ Problem,
+ ProblemPointsVote,
+ Profile,
+ Submission,
+ WebAuthnCredential,
+)
from judge.utils.mail import validate_email_domain
from judge.utils.subscription import newsletter_id
-from judge.widgets import HeavyPreviewPageDownWidget, Select2MultipleWidget, Select2Widget
+from judge.widgets import (
+ HeavyPreviewPageDownWidget,
+ Select2MultipleWidget,
+ Select2Widget,
+)
TOTP_CODE_LENGTH = 6
@@ -28,15 +47,22 @@
TOTP_CODE_LENGTH: {
'regex_validator': RegexValidator(
f'^[0-9]{{{TOTP_CODE_LENGTH}}}$',
- format_lazy(ngettext_lazy('Two-factor authentication tokens must be {count} decimal digit.',
- 'Two-factor authentication tokens must be {count} decimal digits.',
- TOTP_CODE_LENGTH), count=TOTP_CODE_LENGTH),
+ format_lazy(
+ ngettext_lazy(
+ 'Two-factor authentication tokens must be {count} decimal digit.',
+ 'Two-factor authentication tokens must be {count} decimal digits.',
+ TOTP_CODE_LENGTH,
+ ),
+ count=TOTP_CODE_LENGTH,
+ ),
),
'verify': lambda code, profile: not profile.check_totp_code(code),
'err': _('Invalid two-factor authentication token.'),
},
16: {
- 'regex_validator': RegexValidator('^[A-Z0-9]{16}$', _('Scratch codes must be 16 Base32 characters.')),
+ 'regex_validator': RegexValidator(
+ '^[A-Z0-9]{16}$', _('Scratch codes must be 16 Base32 characters.')
+ ),
'verify': lambda code, profile: code not in json.loads(profile.scratch_codes),
'err': _('Invalid scratch code.'),
},
@@ -45,12 +71,24 @@
class ProfileForm(ModelForm):
if newsletter_id is not None:
- newsletter = forms.BooleanField(label=_('Subscribe to contest updates'), initial=False, required=False)
- test_site = forms.BooleanField(label=_('Enable experimental features'), initial=False, required=False)
+ newsletter = forms.BooleanField(
+ label=_('Subscribe to contest updates'), initial=False, required=False
+ )
+ test_site = forms.BooleanField(
+ label=_('Enable experimental features'), initial=False, required=False
+ )
class Meta:
model = Profile
- fields = ['about', 'organizations', 'timezone', 'language', 'ace_theme', 'site_theme', 'user_script']
+ fields = [
+ 'about',
+ 'organizations',
+ 'timezone',
+ 'language',
+ 'ace_theme',
+ 'site_theme',
+ 'user_script',
+ ]
widgets = {
'timezone': Select2Widget(attrs={'style': 'width:200px'}),
'language': Select2Widget(attrs={'style': 'width:200px'}),
@@ -71,7 +109,11 @@ class Meta:
def clean_about(self):
if 'about' in self.changed_data and not self.instance.has_any_solves:
- raise ValidationError(_('You must solve at least one problem before you can update your profile.'))
+ raise ValidationError(
+ _(
+ 'You must solve at least one problem before you can update your profile.'
+ )
+ )
return self.cleaned_data['about']
def clean(self):
@@ -79,9 +121,13 @@ def clean(self):
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
if sum(org.is_open for org in organizations) > max_orgs:
- raise ValidationError(ngettext_lazy('You may not be part of more than {count} public organization.',
- 'You may not be part of more than {count} public organizations.',
- max_orgs).format(count=max_orgs))
+ raise ValidationError(
+ ngettext_lazy(
+ 'You may not be part of more than {count} public organization.',
+ 'You may not be part of more than {count} public organizations.',
+ max_orgs,
+ ).format(count=max_orgs)
+ )
return self.cleaned_data
@@ -94,7 +140,9 @@ def __init__(self, *args, **kwargs):
)
if not self.fields['organizations'].queryset:
self.fields.pop('organizations')
- self.fields['user_script'].widget = AceWidget(mode='javascript', theme=user.profile.resolved_ace_theme)
+ self.fields['user_script'].widget = AceWidget(
+ mode='javascript', theme=user.profile.resolved_ace_theme
+ )
class EmailChangeForm(Form):
@@ -120,11 +168,16 @@ def clean_password(self):
class DownloadDataForm(Form):
comment_download = BooleanField(required=False, label=_('Download comments?'))
submission_download = BooleanField(required=False, label=_('Download submissions?'))
- submission_problem_glob = CharField(initial='*', label=_('Filter by problem code glob:'), max_length=100)
+ submission_problem_glob = CharField(
+ initial='*', label=_('Filter by problem code glob:'), max_length=100
+ )
submission_results = MultipleChoiceField(
required=False,
widget=Select2MultipleWidget(
- attrs={'style': 'width: 260px', 'data-placeholder': _('Leave empty to include all submissions')},
+ attrs={
+ 'style': 'width: 260px',
+ 'data-placeholder': _('Leave empty to include all submissions'),
+ },
),
choices=sorted(map(itemgetter(0, 0), Submission.RESULT)),
label=_('Filter by result:'),
@@ -148,14 +201,18 @@ def clean_submission_result(self):
class ProblemSubmitForm(ModelForm):
- source = CharField(max_length=65536, widget=AceWidget(theme='twilight', no_ace_media=True))
+ source = CharField(
+ max_length=65536, widget=AceWidget(theme='twilight', no_ace_media=True)
+ )
judge = ChoiceField(choices=(), widget=forms.HiddenInput(), required=False)
def __init__(self, *args, judge_choices=(), **kwargs):
super(ProblemSubmitForm, self).__init__(*args, **kwargs)
self.fields['language'].empty_label = None
self.fields['language'].label_from_instance = attrgetter('display_name')
- self.fields['language'].queryset = Language.objects.filter(judges__online=True).distinct()
+ self.fields['language'].queryset = Language.objects.filter(
+ judges__online=True
+ ).distinct()
if judge_choices:
self.fields['judge'].widget = Select2Widget(
@@ -174,7 +231,9 @@ class Meta:
fields = ['about', 'logo_override_image', 'admins']
widgets = {'admins': Select2MultipleWidget(attrs={'style': 'width: 200px'})}
if HeavyPreviewPageDownWidget is not None:
- widgets['about'] = HeavyPreviewPageDownWidget(preview=reverse_lazy('organization_preview'))
+ widgets['about'] = HeavyPreviewPageDownWidget(
+ preview=reverse_lazy('organization_preview')
+ )
class CustomAuthenticationForm(AuthenticationForm):
@@ -188,8 +247,9 @@ def __init__(self, *args, **kwargs):
self.has_github_auth = self._has_social_auth('GITHUB_SECURE')
def _has_social_auth(self, key):
- return (getattr(settings, 'SOCIAL_AUTH_%s_KEY' % key, None) and
- getattr(settings, 'SOCIAL_AUTH_%s_SECRET' % key, None))
+ return getattr(settings, 'SOCIAL_AUTH_%s_KEY' % key, None) and getattr(
+ settings, 'SOCIAL_AUTH_%s_SECRET' % key, None
+ )
class NoAutoCompleteCharField(forms.CharField):
@@ -202,7 +262,9 @@ def widget_attrs(self, widget):
class TOTPForm(Form):
TOLERANCE = settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES
- totp_or_scratch_code = NoAutoCompleteCharField(required=False, widget=forms.TextInput(attrs={'autofocus': True}))
+ totp_or_scratch_code = NoAutoCompleteCharField(
+ required=False, widget=forms.TextInput(attrs={'autofocus': True})
+ )
def __init__(self, *args, **kwargs):
self.profile = kwargs.pop('profile')
@@ -228,7 +290,9 @@ def clean(self):
totp_validate = two_factor_validators_by_length[TOTP_CODE_LENGTH]
code = self.cleaned_data.get('totp_or_scratch_code')
totp_validate['regex_validator'](code)
- if not pyotp.TOTP(self.totp_key).verify(code, valid_window=settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES):
+ if not pyotp.TOTP(self.totp_key).verify(
+ code, valid_window=settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES
+ ):
raise ValidationError(totp_validate['err'])
@@ -242,7 +306,9 @@ def __init__(self, *args, **kwargs):
def clean(self):
totp_or_scratch_code = self.cleaned_data.get('totp_or_scratch_code')
- if self.profile.is_webauthn_enabled and self.cleaned_data.get('webauthn_response'):
+ if self.profile.is_webauthn_enabled and self.cleaned_data.get(
+ 'webauthn_response'
+ ):
if len(self.cleaned_data['webauthn_response']) > 65536:
raise ValidationError(_('Invalid WebAuthn response.'))
@@ -251,7 +317,9 @@ def clean(self):
response = json.loads(self.cleaned_data['webauthn_response'])
try:
- credential = self.profile.webauthn_credentials.get(cred_id=response.get('id', ''))
+ credential = self.profile.webauthn_credentials.get(
+ cred_id=response.get('id', '')
+ )
except WebAuthnCredential.DoesNotExist:
raise ValidationError(_('Invalid WebAuthn credential ID.'))
@@ -274,24 +342,37 @@ def clean(self):
credential.counter = sign_count
credential.save(update_fields=['counter'])
elif totp_or_scratch_code:
- if self.profile.is_totp_enabled and self.profile.check_totp_code(totp_or_scratch_code):
+ if self.profile.is_totp_enabled and self.profile.check_totp_code(
+ totp_or_scratch_code
+ ):
return
- elif self.profile.scratch_codes and totp_or_scratch_code in json.loads(self.profile.scratch_codes):
+ elif self.profile.scratch_codes and totp_or_scratch_code in json.loads(
+ self.profile.scratch_codes
+ ):
scratch_codes = json.loads(self.profile.scratch_codes)
scratch_codes.remove(totp_or_scratch_code)
self.profile.scratch_codes = json.dumps(scratch_codes)
self.profile.save(update_fields=['scratch_codes'])
return
elif self.profile.is_totp_enabled:
- raise ValidationError(_('Invalid two-factor authentication token or scratch code.'))
+ raise ValidationError(
+ _('Invalid two-factor authentication token or scratch code.')
+ )
else:
raise ValidationError(_('Invalid scratch code.'))
else:
- raise ValidationError(_('Must specify either totp_token or webauthn_response.'))
+ raise ValidationError(
+ _('Must specify either totp_token or webauthn_response.')
+ )
class ProblemCloneForm(Form):
- code = CharField(max_length=20, validators=[RegexValidator('^[a-z0-9]+$', _('Problem code must be ^[a-z0-9]+$'))])
+ code = CharField(
+ max_length=20,
+ validators=[
+ RegexValidator('^[a-z0-9]+$', _('Problem code must be ^[a-z0-9]+$'))
+ ],
+ )
def clean_code(self):
code = self.cleaned_data['code']
@@ -301,7 +382,10 @@ def clean_code(self):
class ContestCloneForm(Form):
- key = CharField(max_length=20, validators=[RegexValidator('^[a-z0-9]+$', _('Contest id must be ^[a-z0-9]+$'))])
+ key = CharField(
+ max_length=20,
+ validators=[RegexValidator('^[a-z0-9]+$', _('Contest id must be ^[a-z0-9]+$'))],
+ )
def clean_key(self):
key = self.cleaned_data['key']
diff --git a/judge/fulltext.py b/judge/fulltext.py
index 5b9f7d3d09..0ee32ea47d 100644
--- a/judge/fulltext.py
+++ b/judge/fulltext.py
@@ -25,20 +25,26 @@ def search(self, query, mode=DEFAULT):
# Get the table name and column names from the model
# in `table_name`.`column_name` style
columns = [meta.get_field(name).column for name in self._search_fields]
- full_names = ['%s.%s' %
- (connection.ops.quote_name(meta.db_table),
- connection.ops.quote_name(column))
- for column in columns]
+ full_names = [
+ '%s.%s'
+ % (
+ connection.ops.quote_name(meta.db_table),
+ connection.ops.quote_name(column),
+ )
+ for column in columns
+ ]
# Create the MATCH...AGAINST expressions
fulltext_columns = ', '.join(full_names)
- match_expr = ('MATCH(%s) AGAINST (%%s%s)' % (fulltext_columns, mode))
+ match_expr = 'MATCH(%s) AGAINST (%%s%s)' % (fulltext_columns, mode)
# Add the extra SELECT and WHERE options
- return self.extra(select={'relevance': match_expr},
- select_params=[query],
- where=[match_expr],
- params=[query])
+ return self.extra(
+ select={'relevance': match_expr},
+ select_params=[query],
+ where=[match_expr],
+ params=[query],
+ )
class SearchManager(models.Manager):
diff --git a/judge/highlight_code.py b/judge/highlight_code.py
index 06a947ed97..056a91ff11 100644
--- a/judge/highlight_code.py
+++ b/judge/highlight_code.py
@@ -14,9 +14,12 @@ def _make_pre_code(code):
import pygments.formatters
import pygments.util
except ImportError:
+
def highlight_code(code, language, cssclass=None):
return _make_pre_code(code)
+
else:
+
def highlight_code(code, language, cssclass='codehilite'):
try:
lexer = pygments.lexers.get_lexer_by_name(language)
@@ -24,5 +27,9 @@ def highlight_code(code, language, cssclass='codehilite'):
return _make_pre_code(code)
return mark_safe(
- pygments.highlight(code, lexer, pygments.formatters.HtmlFormatter(cssclass=cssclass, wrapcode=True)),
+ pygments.highlight(
+ code,
+ lexer,
+ pygments.formatters.HtmlFormatter(cssclass=cssclass, wrapcode=True),
+ ),
)
diff --git a/judge/jinja2/__init__.py b/judge/jinja2/__init__.py
index fa556db911..6389bc2a3e 100644
--- a/judge/jinja2/__init__.py
+++ b/judge/jinja2/__init__.py
@@ -8,8 +8,22 @@
from judge.highlight_code import highlight_code
from judge.user_translations import gettext
-from . import (camo, datetime, filesize, format, gravatar, language, markdown, rating, reference, render, social,
- spaceless, submission, timedelta)
+from . import (
+ camo,
+ datetime,
+ filesize,
+ format,
+ gravatar,
+ language,
+ markdown,
+ rating,
+ reference,
+ render,
+ social,
+ spaceless,
+ submission,
+ timedelta,
+)
from . import registry
registry.function('str', str)
diff --git a/judge/jinja2/datetime.py b/judge/jinja2/datetime.py
index 60b3f9753c..039430949b 100644
--- a/judge/jinja2/datetime.py
+++ b/judge/jinja2/datetime.py
@@ -27,6 +27,8 @@ def wrapper(datetime, *args, **kwargs):
@registry.function
def relative_time(time, **kwargs):
abs_time = date(time, kwargs.get('format', _('N j, Y, g:i a')))
- return mark_safe(f''
- f'{escape(kwargs.get("abs", _("on {time}")).replace("{time}", abs_time))} ')
+ return mark_safe(
+ f''
+ f'{escape(kwargs.get("abs", _("on {time}")).replace("{time}", abs_time))} '
+ )
diff --git a/judge/jinja2/filesize.py b/judge/jinja2/filesize.py
index 7b27fdebb7..e6352618e5 100644
--- a/judge/jinja2/filesize.py
+++ b/judge/jinja2/filesize.py
@@ -28,7 +28,11 @@ def _format_size(bytes, callback):
@registry.filter
def kbdetailformat(bytes):
- return avoid_wrapping(_format_size(bytes * 1024, lambda x, y: ['%d %sB', '%.2f %sB'][bool(x)] % (y, x)))
+ return avoid_wrapping(
+ _format_size(
+ bytes * 1024, lambda x, y: ['%d %sB', '%.2f %sB'][bool(x)] % (y, x)
+ )
+ )
@registry.filter
diff --git a/judge/jinja2/gravatar.py b/judge/jinja2/gravatar.py
index 259bf3ef59..638e79a3a3 100644
--- a/judge/jinja2/gravatar.py
+++ b/judge/jinja2/gravatar.py
@@ -17,7 +17,11 @@ def gravatar(email, size=80, default=None):
elif isinstance(email, AbstractUser):
email = email.email
- gravatar_url = 'https://www.gravatar.com/avatar/' + hashlib.md5(utf8bytes(email.strip().lower())).hexdigest() + '?'
+ gravatar_url = (
+ 'https://www.gravatar.com/avatar/'
+ + hashlib.md5(utf8bytes(email.strip().lower())).hexdigest()
+ + '?'
+ )
args = {'d': 'identicon', 's': str(size)}
if default:
args['f'] = 'y'
diff --git a/judge/jinja2/markdown/__init__.py b/judge/jinja2/markdown/__init__.py
index 6bb8d689b3..c01f8f9e47 100644
--- a/judge/jinja2/markdown/__init__.py
+++ b/judge/jinja2/markdown/__init__.py
@@ -71,7 +71,12 @@ def link(self, link, title, text):
if not title:
return '%s ' % (link, self._link_rel(link), text)
title = mistune.escape(title, quote=True)
- return '%s ' % (link, title, self._link_rel(link), text)
+ return '%s ' % (
+ link,
+ title,
+ self._link_rel(link),
+ text,
+ )
def block_code(self, code, lang=None):
if not lang:
@@ -80,23 +85,29 @@ def block_code(self, code, lang=None):
def block_html(self, html):
if self.texoid and html.startswith('')]
- latex = html[html.index('>') + 1:html.rindex('<')]
+ attr = html[6 : html.index('>')]
+ latex = html[html.index('>') + 1 : html.rindex('<')]
latex = unescape(latex)
result = self.texoid.get_result(latex)
if not result:
return '%s ' % mistune.escape(latex, smart_amp=False)
elif 'error' not in result:
- img = (''' ') % {
- 'svg': result['svg'], 'png': result['png'],
- 'width': result['meta']['width'], 'height': result['meta']['height'],
+ img = (
+ ''' '
+ ) % {
+ 'svg': result['svg'],
+ 'png': result['png'],
+ 'width': result['meta']['width'],
+ 'height': result['meta']['height'],
'tail': ' /' if self.options.get('use_xhtml') else '',
}
- style = ['max-width: 100%',
- 'height: %s' % result['meta']['height'],
- 'max-height: %s' % result['meta']['height'],
- 'width: %s' % result['meta']['width']]
+ style = [
+ 'max-width: 100%',
+ 'height: %s' % result['meta']['height'],
+ 'max-height: %s' % result['meta']['height'],
+ 'width: %s' % result['meta']['width'],
+ ]
if 'inline' in attr:
tag = 'span'
else:
@@ -104,7 +115,9 @@ def block_html(self, html):
style += ['text-align: center']
return '<%s style="%s">%s%s>' % (tag, ';'.join(style), img, tag)
else:
- return '%s ' % mistune.escape(result['error'], smart_amp=False)
+ return '%s ' % mistune.escape(
+ result['error'], smart_amp=False
+ )
return super(AwesomeRenderer, self).block_html(html)
def header(self, text, level, *args, **kwargs):
@@ -120,7 +133,9 @@ def get_cleaner(name, params):
styles = params.pop('styles', None)
if styles:
- params['css_sanitizer'] = CSSSanitizer(allowed_css_properties=all_styles if styles is True else styles)
+ params['css_sanitizer'] = CSSSanitizer(
+ allowed_css_properties=all_styles if styles is True else styles
+ )
if params.pop('mathml', False):
params['tags'] = params.get('tags', []) + mathml_tags
@@ -134,9 +149,13 @@ def get_cleaner(name, params):
def fragments_to_tree(fragment):
tree = html.Element('div')
try:
- parsed = html.fragments_fromstring(fragment, parser=html.HTMLParser(recover=True))
+ parsed = html.fragments_fromstring(
+ fragment, parser=html.HTMLParser(recover=True)
+ )
except (XMLSyntaxError, ParserError) as e:
- if fragment and (not isinstance(e, ParserError) or e.args[0] != 'Document is empty'):
+ if fragment and (
+ not isinstance(e, ParserError) or e.args[0] != 'Document is empty'
+ ):
logger.exception('Failed to parse HTML string')
return tree
@@ -161,7 +180,7 @@ def strip_paragraphs_tags(tree):
def fragment_tree_to_str(tree):
- return html.tostring(tree, encoding='unicode')[len(''):-len('
')]
+ return html.tostring(tree, encoding='unicode')[len('') : -len('
')]
@registry.filter
@@ -179,10 +198,19 @@ def markdown(value, style, math_engine=None, lazy_load=False, strip_paragraphs=F
if lazy_load:
post_processors.append(lazy_load_processor)
- renderer = AwesomeRenderer(escape=escape, nofollow=nofollow, texoid=texoid,
- math=math and math_engine is not None, math_engine=math_engine)
- markdown = mistune.Markdown(renderer=renderer, inline=AwesomeInlineLexer,
- parse_block_html=1, parse_inline_html=1)
+ renderer = AwesomeRenderer(
+ escape=escape,
+ nofollow=nofollow,
+ texoid=texoid,
+ math=math and math_engine is not None,
+ math_engine=math_engine,
+ )
+ markdown = mistune.Markdown(
+ renderer=renderer,
+ inline=AwesomeInlineLexer,
+ parse_block_html=1,
+ parse_inline_html=1,
+ )
result = markdown(value)
if post_processors or strip_paragraphs:
diff --git a/judge/jinja2/markdown/bleach_whitelist.py b/judge/jinja2/markdown/bleach_whitelist.py
index 3aed9d9730..560f03f2d7 100644
--- a/judge/jinja2/markdown/bleach_whitelist.py
+++ b/judge/jinja2/markdown/bleach_whitelist.py
@@ -5,347 +5,1358 @@
# This includes pseudo-classes, pseudo-elements, @-rules, units, and
# selectors in addition to properties, but it doesn't matter for our
# purposes -- we don't need to filter styles..
- ':active', '::after (:after)', 'align-content', 'align-items', 'align-self',
- 'all', '', 'animation', 'animation-delay', 'animation-direction',
- 'animation-duration', 'animation-fill-mode', 'animation-iteration-count',
- 'animation-name', 'animation-play-state', 'animation-timing-function',
- '@annotation', 'annotation()', 'attr()', '::backdrop', 'backface-visibility',
- 'background', 'background-attachment', 'background-blend-mode',
- 'background-clip', 'background-color', 'background-image', 'background-origin',
- 'background-position', 'background-repeat', 'background-size', '',
- '::before (:before)', '', 'blur()', 'border', 'border-bottom',
- 'border-bottom-color', 'border-bottom-left-radius',
- 'border-bottom-right-radius', 'border-bottom-style', 'border-bottom-width',
- 'border-collapse', 'border-color', 'border-image', 'border-image-outset',
- 'border-image-repeat', 'border-image-slice', 'border-image-source',
- 'border-image-width', 'border-left', 'border-left-color', 'border-left-style',
- 'border-left-width', 'border-radius', 'border-right', 'border-right-color',
- 'border-right-style', 'border-right-width', 'border-spacing', 'border-style',
- 'border-top', 'border-top-color', 'border-top-left-radius',
- 'border-top-right-radius', 'border-top-style', 'border-top-width',
- 'border-width', 'bottom', 'box-decoration-break', 'box-shadow', 'box-sizing',
- 'break-after', 'break-before', 'break-inside', 'brightness()', 'calc()',
- 'caption-side', 'ch', '@character-variant', 'character-variant()', '@charset',
- ':checked', 'circle()', 'clear', 'clip', 'clip-path', 'cm', 'color', '',
- 'columns', 'column-count', 'column-fill', 'column-gap', 'column-rule',
- 'column-rule-color', 'column-rule-style', 'column-rule-width', 'column-span',
- 'column-width', 'content', 'contrast()', '', 'counter-increment',
- 'counter-reset', '@counter-style', 'cubic-bezier()', 'cursor',
- '', ':default', 'deg', ':dir()', 'direction', ':disabled',
- 'display', '@document', 'dpcm', 'dpi', 'dppx', 'drop-shadow()', 'element()',
- 'ellipse()', 'em', ':empty', 'empty-cells', ':enabled', 'ex', 'filter',
- ':first', ':first-child', '::first-letter', '::first-line',
- ':first-of-type', 'flex', 'flex-basis', 'flex-direction',
- 'flex-flow', 'flex-grow', 'flex-shrink', 'flex-wrap', 'float', ':focus',
- 'font', '@font-face', 'font-family', 'font-feature-settings',
- '@font-feature-values', 'font-kerning', 'font-language-override', 'font-size',
- 'font-size-adjust', 'font-stretch', 'font-style', 'font-synthesis',
- 'font-variant', 'font-variant-alternates', 'font-variant-caps',
- 'font-variant-east-asian', 'font-variant-ligatures', 'font-variant-numeric',
- 'font-variant-position', 'font-weight', '', ':fullscreen', 'grad',
- '', 'grayscale()', 'grid', 'grid-area', 'grid-auto-columns',
- 'grid-auto-flow', 'grid-auto-position', 'grid-auto-rows', 'grid-column',
- 'grid-column-start', 'grid-column-end', 'grid-row', 'grid-row-start',
- 'grid-row-end', 'grid-template', 'grid-template-areas', 'grid-template-rows',
- 'grid-template-columns', 'height', ':hover', 'hsl()', 'hsla()', 'hue-rotate()',
- 'hyphens', 'hz', '', 'image()', 'image-rendering', 'image-resolution',
- 'image-orientation', 'ime-mode', '@import', 'in', ':indeterminate', 'inherit',
- 'initial', ':in-range', 'inset()', '', ':invalid', 'invert()',
- 'isolation', 'justify-content', '@keyframes', 'khz', ':lang()', ':last-child',
- ':last-of-type', 'left', ':left', '', 'letter-spacing',
- 'linear-gradient()', 'line-break', 'line-height', ':link', 'list-style',
- 'list-style-image', 'list-style-position', 'list-style-type', 'margin',
- 'margin-bottom', 'margin-left', 'margin-right', 'margin-top', 'marks', 'mask',
- 'mask-type', 'matrix()', 'matrix3d()', 'max-height', 'max-width', '@media',
- 'min-height', 'minmax()', 'min-width', 'mix-blend-mode', 'mm', 'ms',
- '@namespace', ':not()', ':nth-child()', ':nth-last-child()',
- ':nth-last-of-type()', ':nth-of-type()', '', 'object-fit',
- 'object-position', ':only-child', ':only-of-type', 'opacity', 'opacity()',
- ':optional', 'order', '@ornaments', 'ornaments()', 'orphans', 'outline',
- 'outline-color', 'outline-offset', 'outline-style', 'outline-width',
- ':out-of-range', 'overflow', 'overflow-wrap', 'overflow-x', 'overflow-y',
- 'padding', 'padding-bottom', 'padding-left', 'padding-right', 'padding-top',
- '@page', 'page-break-after', 'page-break-before', 'page-break-inside', 'pc',
- '', 'perspective', 'perspective()', 'perspective-origin',
- 'pointer-events', 'polygon()', 'position', '', 'pt', 'px', 'quotes',
- 'rad', 'radial-gradient()', '', ':read-only', ':read-write', 'rect()',
- 'rem', 'repeat()', '::repeat-index', '::repeat-item',
- 'repeating-linear-gradient()', 'repeating-radial-gradient()', ':required',
- 'resize', '', 'rgb()', 'rgba()', 'right', ':right', ':root',
- 'rotate()', 'rotatex()', 'rotatey()', 'rotatez()', 'rotate3d()', 'ruby-align',
- 'ruby-merge', 'ruby-position', 's', 'saturate()', 'scale()', 'scalex()',
- 'scaley()', 'scalez()', 'scale3d()', ':scope', 'scroll-behavior',
- '::selection', 'sepia()', '', 'shape-image-threshold', 'shape-margin',
- 'shape-outside', 'skew()', 'skewx()', 'skewy()', 'steps()', '',
- '@styleset', 'styleset()', '@stylistic', 'stylistic()', '@supports', '@swash',
- 'swash()', 'symbol()', 'table-layout', 'tab-size', ':target', 'text-align',
- 'text-align-last', 'text-combine-upright', 'text-decoration',
- 'text-decoration-color', 'text-decoration-line', 'text-decoration-style',
- 'text-indent', 'text-orientation', 'text-overflow', 'text-rendering',
- 'text-shadow', 'text-transform', 'text-underline-position', '',
- '', 'top', 'touch-action', 'transform', 'transform-origin',
- 'transform-style', 'transition', 'transition-delay', 'transition-duration',
- 'transition-property', 'transition-timing-function', 'translate()',
- 'translatex()', 'translatey()', 'translatez()', 'translate3d()', 'turn',
- 'unicode-bidi', 'unicode-range', 'unset', '', 'url()', '',
- ':valid', '::value', 'var()', 'vertical-align', 'vh', '@viewport',
- 'visibility', ':visited', 'vmax', 'vmin', 'vw', 'white-space', 'widows',
- 'width', 'will-change', 'word-break', 'word-spacing', 'word-wrap',
- 'writing-mode', 'z-index',
+ ':active',
+ '::after (:after)',
+ 'align-content',
+ 'align-items',
+ 'align-self',
+ 'all',
+ '',
+ 'animation',
+ 'animation-delay',
+ 'animation-direction',
+ 'animation-duration',
+ 'animation-fill-mode',
+ 'animation-iteration-count',
+ 'animation-name',
+ 'animation-play-state',
+ 'animation-timing-function',
+ '@annotation',
+ 'annotation()',
+ 'attr()',
+ '::backdrop',
+ 'backface-visibility',
+ 'background',
+ 'background-attachment',
+ 'background-blend-mode',
+ 'background-clip',
+ 'background-color',
+ 'background-image',
+ 'background-origin',
+ 'background-position',
+ 'background-repeat',
+ 'background-size',
+ '',
+ '::before (:before)',
+ '',
+ 'blur()',
+ 'border',
+ 'border-bottom',
+ 'border-bottom-color',
+ 'border-bottom-left-radius',
+ 'border-bottom-right-radius',
+ 'border-bottom-style',
+ 'border-bottom-width',
+ 'border-collapse',
+ 'border-color',
+ 'border-image',
+ 'border-image-outset',
+ 'border-image-repeat',
+ 'border-image-slice',
+ 'border-image-source',
+ 'border-image-width',
+ 'border-left',
+ 'border-left-color',
+ 'border-left-style',
+ 'border-left-width',
+ 'border-radius',
+ 'border-right',
+ 'border-right-color',
+ 'border-right-style',
+ 'border-right-width',
+ 'border-spacing',
+ 'border-style',
+ 'border-top',
+ 'border-top-color',
+ 'border-top-left-radius',
+ 'border-top-right-radius',
+ 'border-top-style',
+ 'border-top-width',
+ 'border-width',
+ 'bottom',
+ 'box-decoration-break',
+ 'box-shadow',
+ 'box-sizing',
+ 'break-after',
+ 'break-before',
+ 'break-inside',
+ 'brightness()',
+ 'calc()',
+ 'caption-side',
+ 'ch',
+ '@character-variant',
+ 'character-variant()',
+ '@charset',
+ ':checked',
+ 'circle()',
+ 'clear',
+ 'clip',
+ 'clip-path',
+ 'cm',
+ 'color',
+ '',
+ 'columns',
+ 'column-count',
+ 'column-fill',
+ 'column-gap',
+ 'column-rule',
+ 'column-rule-color',
+ 'column-rule-style',
+ 'column-rule-width',
+ 'column-span',
+ 'column-width',
+ 'content',
+ 'contrast()',
+ '',
+ 'counter-increment',
+ 'counter-reset',
+ '@counter-style',
+ 'cubic-bezier()',
+ 'cursor',
+ '',
+ ':default',
+ 'deg',
+ ':dir()',
+ 'direction',
+ ':disabled',
+ 'display',
+ '@document',
+ 'dpcm',
+ 'dpi',
+ 'dppx',
+ 'drop-shadow()',
+ 'element()',
+ 'ellipse()',
+ 'em',
+ ':empty',
+ 'empty-cells',
+ ':enabled',
+ 'ex',
+ 'filter',
+ ':first',
+ ':first-child',
+ '::first-letter',
+ '::first-line',
+ ':first-of-type',
+ 'flex',
+ 'flex-basis',
+ 'flex-direction',
+ 'flex-flow',
+ 'flex-grow',
+ 'flex-shrink',
+ 'flex-wrap',
+ 'float',
+ ':focus',
+ 'font',
+ '@font-face',
+ 'font-family',
+ 'font-feature-settings',
+ '@font-feature-values',
+ 'font-kerning',
+ 'font-language-override',
+ 'font-size',
+ 'font-size-adjust',
+ 'font-stretch',
+ 'font-style',
+ 'font-synthesis',
+ 'font-variant',
+ 'font-variant-alternates',
+ 'font-variant-caps',
+ 'font-variant-east-asian',
+ 'font-variant-ligatures',
+ 'font-variant-numeric',
+ 'font-variant-position',
+ 'font-weight',
+ '',
+ ':fullscreen',
+ 'grad',
+ '',
+ 'grayscale()',
+ 'grid',
+ 'grid-area',
+ 'grid-auto-columns',
+ 'grid-auto-flow',
+ 'grid-auto-position',
+ 'grid-auto-rows',
+ 'grid-column',
+ 'grid-column-start',
+ 'grid-column-end',
+ 'grid-row',
+ 'grid-row-start',
+ 'grid-row-end',
+ 'grid-template',
+ 'grid-template-areas',
+ 'grid-template-rows',
+ 'grid-template-columns',
+ 'height',
+ ':hover',
+ 'hsl()',
+ 'hsla()',
+ 'hue-rotate()',
+ 'hyphens',
+ 'hz',
+ '',
+ 'image()',
+ 'image-rendering',
+ 'image-resolution',
+ 'image-orientation',
+ 'ime-mode',
+ '@import',
+ 'in',
+ ':indeterminate',
+ 'inherit',
+ 'initial',
+ ':in-range',
+ 'inset()',
+ '',
+ ':invalid',
+ 'invert()',
+ 'isolation',
+ 'justify-content',
+ '@keyframes',
+ 'khz',
+ ':lang()',
+ ':last-child',
+ ':last-of-type',
+ 'left',
+ ':left',
+ '',
+ 'letter-spacing',
+ 'linear-gradient()',
+ 'line-break',
+ 'line-height',
+ ':link',
+ 'list-style',
+ 'list-style-image',
+ 'list-style-position',
+ 'list-style-type',
+ 'margin',
+ 'margin-bottom',
+ 'margin-left',
+ 'margin-right',
+ 'margin-top',
+ 'marks',
+ 'mask',
+ 'mask-type',
+ 'matrix()',
+ 'matrix3d()',
+ 'max-height',
+ 'max-width',
+ '@media',
+ 'min-height',
+ 'minmax()',
+ 'min-width',
+ 'mix-blend-mode',
+ 'mm',
+ 'ms',
+ '@namespace',
+ ':not()',
+ ':nth-child()',
+ ':nth-last-child()',
+ ':nth-last-of-type()',
+ ':nth-of-type()',
+ '',
+ 'object-fit',
+ 'object-position',
+ ':only-child',
+ ':only-of-type',
+ 'opacity',
+ 'opacity()',
+ ':optional',
+ 'order',
+ '@ornaments',
+ 'ornaments()',
+ 'orphans',
+ 'outline',
+ 'outline-color',
+ 'outline-offset',
+ 'outline-style',
+ 'outline-width',
+ ':out-of-range',
+ 'overflow',
+ 'overflow-wrap',
+ 'overflow-x',
+ 'overflow-y',
+ 'padding',
+ 'padding-bottom',
+ 'padding-left',
+ 'padding-right',
+ 'padding-top',
+ '@page',
+ 'page-break-after',
+ 'page-break-before',
+ 'page-break-inside',
+ 'pc',
+ '',
+ 'perspective',
+ 'perspective()',
+ 'perspective-origin',
+ 'pointer-events',
+ 'polygon()',
+ 'position',
+ '',
+ 'pt',
+ 'px',
+ 'quotes',
+ 'rad',
+ 'radial-gradient()',
+ '',
+ ':read-only',
+ ':read-write',
+ 'rect()',
+ 'rem',
+ 'repeat()',
+ '::repeat-index',
+ '::repeat-item',
+ 'repeating-linear-gradient()',
+ 'repeating-radial-gradient()',
+ ':required',
+ 'resize',
+ '',
+ 'rgb()',
+ 'rgba()',
+ 'right',
+ ':right',
+ ':root',
+ 'rotate()',
+ 'rotatex()',
+ 'rotatey()',
+ 'rotatez()',
+ 'rotate3d()',
+ 'ruby-align',
+ 'ruby-merge',
+ 'ruby-position',
+ 's',
+ 'saturate()',
+ 'scale()',
+ 'scalex()',
+ 'scaley()',
+ 'scalez()',
+ 'scale3d()',
+ ':scope',
+ 'scroll-behavior',
+ '::selection',
+ 'sepia()',
+ '',
+ 'shape-image-threshold',
+ 'shape-margin',
+ 'shape-outside',
+ 'skew()',
+ 'skewx()',
+ 'skewy()',
+ 'steps()',
+ '',
+ '@styleset',
+ 'styleset()',
+ '@stylistic',
+ 'stylistic()',
+ '@supports',
+ '@swash',
+ 'swash()',
+ 'symbol()',
+ 'table-layout',
+ 'tab-size',
+ ':target',
+ 'text-align',
+ 'text-align-last',
+ 'text-combine-upright',
+ 'text-decoration',
+ 'text-decoration-color',
+ 'text-decoration-line',
+ 'text-decoration-style',
+ 'text-indent',
+ 'text-orientation',
+ 'text-overflow',
+ 'text-rendering',
+ 'text-shadow',
+ 'text-transform',
+ 'text-underline-position',
+ '',
+ '',
+ 'top',
+ 'touch-action',
+ 'transform',
+ 'transform-origin',
+ 'transform-style',
+ 'transition',
+ 'transition-delay',
+ 'transition-duration',
+ 'transition-property',
+ 'transition-timing-function',
+ 'translate()',
+ 'translatex()',
+ 'translatey()',
+ 'translatez()',
+ 'translate3d()',
+ 'turn',
+ 'unicode-bidi',
+ 'unicode-range',
+ 'unset',
+ '',
+ 'url()',
+ '',
+ ':valid',
+ '::value',
+ 'var()',
+ 'vertical-align',
+ 'vh',
+ '@viewport',
+ 'visibility',
+ ':visited',
+ 'vmax',
+ 'vmin',
+ 'vw',
+ 'white-space',
+ 'widows',
+ 'width',
+ 'will-change',
+ 'word-break',
+ 'word-spacing',
+ 'word-wrap',
+ 'writing-mode',
+ 'z-index',
]
all_prefixed_styles = [
# From http://peter.sh/experiments/vendor-prefixed-css-property-overview/
- '-ms-accelerator', '-webkit-app-region', '-webkit-appearance',
- '-webkit-appearance', '-moz-appearance', '-webkit-aspect-ratio',
- '-webkit-backdrop-filter', 'backface-visibility',
- '-webkit-backface-visibility', 'backface-visibility', 'backface-visibility',
- '-webkit-background-composite', '-webkit-background-composite', '-moz-binding',
- '-ms-block-progression', '-webkit-border-after', '-webkit-border-after',
- '-webkit-border-after-color', '-webkit-border-after-color',
- '-webkit-border-after-style', '-webkit-border-after-style',
- '-webkit-border-after-width', '-webkit-border-after-width',
- '-webkit-border-before', '-webkit-border-before',
- '-webkit-border-before-color', '-webkit-border-before-color',
- '-webkit-border-before-style', '-webkit-border-before-style',
- '-webkit-border-before-width', '-webkit-border-before-width',
- '-moz-border-bottom-colors', '-webkit-border-end', '-webkit-border-end',
- '-moz-border-end', '-webkit-border-end-color', '-webkit-border-end-color',
- '-moz-border-end-color', '-webkit-border-end-style',
- '-webkit-border-end-style', '-moz-border-end-style',
- '-webkit-border-end-width', '-webkit-border-end-width',
- '-moz-border-end-width', '-webkit-border-fit',
- '-webkit-border-horizontal-spacing', '-webkit-border-horizontal-spacing',
- '-moz-border-left-colors', '-moz-border-right-colors', '-webkit-border-start',
- '-webkit-border-start', '-moz-border-start', '-webkit-border-start-color',
- '-webkit-border-start-color', '-moz-border-start-color',
- '-webkit-border-start-style', '-webkit-border-start-style',
- '-moz-border-start-style', '-webkit-border-start-width',
- '-webkit-border-start-width', '-moz-border-start-width',
- '-moz-border-top-colors', '-webkit-border-vertical-spacing',
- '-webkit-border-vertical-spacing', '-webkit-box-align', '-webkit-box-align',
- '-moz-box-align', '-webkit-box-decoration-break',
- '-webkit-box-decoration-break', 'box-decoration-break',
- '-webkit-box-direction', '-webkit-box-direction', '-moz-box-direction',
- '-webkit-box-flex', '-webkit-box-flex', '-moz-box-flex',
- '-webkit-box-flex-group', '-webkit-box-flex-group', '-webkit-box-lines',
- '-webkit-box-lines', '-webkit-box-ordinal-group', '-webkit-box-ordinal-group',
- '-moz-box-ordinal-group', '-webkit-box-orient', '-webkit-box-orient',
- '-moz-box-orient', '-webkit-box-pack', '-webkit-box-pack', '-moz-box-pack',
- '-webkit-box-reflect', '-webkit-box-reflect', 'clip-path', '-webkit-clip-path',
- 'clip-path', 'clip-path', '-webkit-color-correction', '-webkit-column-axis',
- '-webkit-column-break-after', '-webkit-column-break-after',
- '-webkit-column-break-before', '-webkit-column-break-before',
- '-webkit-column-break-inside', '-webkit-column-break-inside',
- '-webkit-column-count', 'column-count', '-moz-column-count', 'column-count',
- 'column-fill', 'column-fill', '-moz-column-fill', 'column-fill',
- '-webkit-column-gap', 'column-gap', '-moz-column-gap', 'column-gap',
- '-webkit-column-rule', 'column-rule', '-moz-column-rule', 'column-rule',
- '-webkit-column-rule-color', 'column-rule-color', '-moz-column-rule-color',
- 'column-rule-color', '-webkit-column-rule-style', 'column-rule-style',
- '-moz-column-rule-style', 'column-rule-style', '-webkit-column-rule-width',
- 'column-rule-width', '-moz-column-rule-width', 'column-rule-width',
- '-webkit-column-span', 'column-span', 'column-span', '-webkit-column-width',
- 'column-width', '-moz-column-width', 'column-width', '-webkit-columns',
- 'columns', '-moz-columns', 'columns', '-ms-content-zoom-chaining',
- '-ms-content-zoom-limit', '-ms-content-zoom-limit-max',
- '-ms-content-zoom-limit-min', '-ms-content-zoom-snap',
- '-ms-content-zoom-snap-points', '-ms-content-zoom-snap-type',
- '-ms-content-zooming', '-moz-control-character-visibility',
- '-webkit-cursor-visibility', '-webkit-dashboard-region', 'filter',
- '-webkit-filter', 'filter', 'filter', '-ms-flex-align', '-ms-flex-item-align',
- '-ms-flex-line-pack', '-ms-flex-negative', '-ms-flex-order', '-ms-flex-pack',
- '-ms-flex-positive', '-ms-flex-preferred-size', '-moz-float-edge',
- '-webkit-flow-from', '-ms-flow-from', '-webkit-flow-into', '-ms-flow-into',
- '-webkit-font-feature-settings', '-webkit-font-feature-settings',
- 'font-feature-settings', 'font-feature-settings', 'font-kerning',
- '-webkit-font-kerning', 'font-kerning', '-webkit-font-size-delta',
- '-webkit-font-size-delta', '-webkit-font-smoothing', '-webkit-font-smoothing',
- 'font-variant-ligatures', '-webkit-font-variant-ligatures',
- 'font-variant-ligatures', '-moz-force-broken-image-icon', 'grid',
- '-webkit-grid', 'grid', 'grid-area', '-webkit-grid-area', 'grid-area',
- 'grid-auto-columns', '-webkit-grid-auto-columns', 'grid-auto-columns',
- 'grid-auto-flow', '-webkit-grid-auto-flow', 'grid-auto-flow', 'grid-auto-rows',
- '-webkit-grid-auto-rows', 'grid-auto-rows', 'grid-column',
- '-webkit-grid-column', 'grid-column', '-ms-grid-column',
- '-ms-grid-column-align', 'grid-column-end', '-webkit-grid-column-end',
- 'grid-column-end', '-ms-grid-column-span', 'grid-column-start',
- '-webkit-grid-column-start', 'grid-column-start', '-ms-grid-columns',
- 'grid-row', '-webkit-grid-row', 'grid-row', '-ms-grid-row',
- '-ms-grid-row-align', 'grid-row-end', '-webkit-grid-row-end', 'grid-row-end',
- '-ms-grid-row-span', 'grid-row-start', '-webkit-grid-row-start',
- 'grid-row-start', '-ms-grid-rows', 'grid-template', '-webkit-grid-template',
- 'grid-template', 'grid-template-areas', '-webkit-grid-template-areas',
- 'grid-template-areas', 'grid-template-columns',
- '-webkit-grid-template-columns', 'grid-template-columns', 'grid-template-rows',
- '-webkit-grid-template-rows', 'grid-template-rows', '-ms-high-contrast-adjust',
- '-webkit-highlight', '-webkit-hyphenate-character',
- '-webkit-hyphenate-character', '-webkit-hyphenate-limit-after',
- '-webkit-hyphenate-limit-before', '-ms-hyphenate-limit-chars',
- '-webkit-hyphenate-limit-lines', '-ms-hyphenate-limit-lines',
- '-ms-hyphenate-limit-zone', '-webkit-hyphens', '-moz-hyphens', '-ms-hyphens',
- '-moz-image-region', '-ms-ime-align', '-webkit-initial-letter',
- '-ms-interpolation-mode', 'justify-self', '-webkit-justify-self',
- '-webkit-line-align', '-webkit-line-box-contain', '-webkit-line-box-contain',
- '-webkit-line-break', '-webkit-line-break', 'line-break', '-webkit-line-clamp',
- '-webkit-line-clamp', '-webkit-line-grid', '-webkit-line-snap',
- '-webkit-locale', '-webkit-locale', '-webkit-logical-height',
- '-webkit-logical-height', '-webkit-logical-width', '-webkit-logical-width',
- '-webkit-margin-after', '-webkit-margin-after',
- '-webkit-margin-after-collapse', '-webkit-margin-after-collapse',
- '-webkit-margin-before', '-webkit-margin-before',
- '-webkit-margin-before-collapse', '-webkit-margin-before-collapse',
- '-webkit-margin-bottom-collapse', '-webkit-margin-bottom-collapse',
- '-webkit-margin-collapse', '-webkit-margin-collapse', '-webkit-margin-end',
- '-webkit-margin-end', '-moz-margin-end', '-webkit-margin-start',
- '-webkit-margin-start', '-moz-margin-start', '-webkit-margin-top-collapse',
- '-webkit-margin-top-collapse', '-webkit-marquee', '-webkit-marquee-direction',
- '-webkit-marquee-increment', '-webkit-marquee-repetition',
- '-webkit-marquee-speed', '-webkit-marquee-style', 'mask', '-webkit-mask',
- 'mask', '-webkit-mask-box-image', '-webkit-mask-box-image',
- '-webkit-mask-box-image-outset', '-webkit-mask-box-image-outset',
- '-webkit-mask-box-image-repeat', '-webkit-mask-box-image-repeat',
- '-webkit-mask-box-image-slice', '-webkit-mask-box-image-slice',
- '-webkit-mask-box-image-source', '-webkit-mask-box-image-source',
- '-webkit-mask-box-image-width', '-webkit-mask-box-image-width',
- '-webkit-mask-clip', '-webkit-mask-clip', '-webkit-mask-composite',
- '-webkit-mask-composite', '-webkit-mask-image', '-webkit-mask-image',
- '-webkit-mask-origin', '-webkit-mask-origin', '-webkit-mask-position',
- '-webkit-mask-position', '-webkit-mask-position-x', '-webkit-mask-position-x',
- '-webkit-mask-position-y', '-webkit-mask-position-y', '-webkit-mask-repeat',
- '-webkit-mask-repeat', '-webkit-mask-repeat-x', '-webkit-mask-repeat-x',
- '-webkit-mask-repeat-y', '-webkit-mask-repeat-y', '-webkit-mask-size',
- '-webkit-mask-size', 'mask-source-type', '-webkit-mask-source-type',
- '-moz-math-display', '-moz-math-variant', '-webkit-max-logical-height',
- '-webkit-max-logical-height', '-webkit-max-logical-width',
- '-webkit-max-logical-width', '-webkit-min-logical-height',
- '-webkit-min-logical-height', '-webkit-min-logical-width',
- '-webkit-min-logical-width', '-webkit-nbsp-mode', '-moz-orient',
- '-moz-osx-font-smoothing', '-moz-outline-radius',
- '-moz-outline-radius-bottomleft', '-moz-outline-radius-bottomright',
- '-moz-outline-radius-topleft', '-moz-outline-radius-topright',
- '-webkit-overflow-scrolling', '-ms-overflow-style', '-webkit-padding-after',
- '-webkit-padding-after', '-webkit-padding-before', '-webkit-padding-before',
- '-webkit-padding-end', '-webkit-padding-end', '-moz-padding-end',
- '-webkit-padding-start', '-webkit-padding-start', '-moz-padding-start',
- 'perspective', '-webkit-perspective', 'perspective', 'perspective',
- 'perspective-origin', '-webkit-perspective-origin', 'perspective-origin',
- 'perspective-origin', '-webkit-perspective-origin-x',
- '-webkit-perspective-origin-x', 'perspective-origin-x',
- '-webkit-perspective-origin-y', '-webkit-perspective-origin-y',
- 'perspective-origin-y', '-webkit-print-color-adjust',
- '-webkit-print-color-adjust', '-webkit-region-break-after',
- '-webkit-region-break-before', '-webkit-region-break-inside',
- '-webkit-region-fragment', '-webkit-rtl-ordering', '-webkit-rtl-ordering',
- '-webkit-ruby-position', '-webkit-ruby-position', 'ruby-position',
- '-moz-script-level', '-moz-script-min-size', '-moz-script-size-multiplier',
- '-ms-scroll-chaining', '-ms-scroll-limit', '-ms-scroll-limit-x-max',
- '-ms-scroll-limit-x-min', '-ms-scroll-limit-y-max', '-ms-scroll-limit-y-min',
- '-ms-scroll-rails', '-webkit-scroll-snap-coordinate',
- '-webkit-scroll-snap-destination', '-webkit-scroll-snap-points-x',
- '-ms-scroll-snap-points-x', '-webkit-scroll-snap-points-y',
- '-ms-scroll-snap-points-y', '-webkit-scroll-snap-type', '-ms-scroll-snap-type',
- '-ms-scroll-snap-x', '-ms-scroll-snap-y', '-ms-scroll-translation',
- '-ms-scrollbar-3dlight-color', 'shape-image-threshold',
- '-webkit-shape-image-threshold', 'shape-margin', '-webkit-shape-margin',
- 'shape-outside', '-webkit-shape-outside', '-moz-stack-sizing', 'tab-size',
- 'tab-size', '-moz-tab-size', '-webkit-tap-highlight-color',
- '-webkit-tap-highlight-color', 'text-align-last', '-webkit-text-align-last',
- '-moz-text-align-last', 'text-align-last', '-webkit-text-combine',
- '-webkit-text-combine', '-ms-text-combine-horizontal', 'text-decoration-color',
- '-webkit-text-decoration-color', 'text-decoration-color',
- 'text-decoration-color', 'text-decoration-line',
- '-webkit-text-decoration-line', 'text-decoration-line',
- '-webkit-text-decoration-skip', 'text-decoration-style',
- '-webkit-text-decoration-style', 'text-decoration-style',
- '-webkit-text-decorations-in-effect', '-webkit-text-decorations-in-effect',
- '-webkit-text-emphasis', 'text-emphasis', '-webkit-text-emphasis-color',
- 'text-emphasis-color', '-webkit-text-emphasis-position',
- 'text-emphasis-position', '-webkit-text-emphasis-style', 'text-emphasis-style',
- '-webkit-text-fill-color', '-webkit-text-fill-color', 'text-justify',
- '-webkit-text-justify', 'text-justify', '-webkit-text-orientation',
- '-webkit-text-orientation', 'text-orientation', '-webkit-text-security',
- '-webkit-text-security', '-webkit-text-size-adjust', '-moz-text-size-adjust',
- '-ms-text-size-adjust', '-webkit-text-stroke', '-webkit-text-stroke',
- '-webkit-text-stroke-color', '-webkit-text-stroke-color',
- '-webkit-text-stroke-width', '-webkit-text-stroke-width',
- 'text-underline-position', '-webkit-text-underline-position',
- 'text-underline-position', '-webkit-touch-callout', '-ms-touch-select',
- 'transform', '-webkit-transform', 'transform', 'transform', 'transform-origin',
- '-webkit-transform-origin', 'transform-origin', 'transform-origin',
- '-webkit-transform-origin-x', '-webkit-transform-origin-x',
- 'transform-origin-x', '-webkit-transform-origin-y',
- '-webkit-transform-origin-y', 'transform-origin-y',
- '-webkit-transform-origin-z', '-webkit-transform-origin-z',
- 'transform-origin-z', 'transform-style', '-webkit-transform-style',
- 'transform-style', 'transform-style', '-webkit-user-drag', '-webkit-user-drag',
- '-moz-user-focus', '-moz-user-input', '-webkit-user-modify',
- '-webkit-user-modify', '-moz-user-modify', '-webkit-user-select',
- '-webkit-user-select', '-moz-user-select', '-ms-user-select',
- '-moz-window-dragging', '-moz-window-shadow', '-ms-wrap-flow',
- '-ms-wrap-margin', '-ms-wrap-through', 'writing-mode', '-webkit-writing-mode',
- 'writing-mode', 'writing-mode',
+ '-ms-accelerator',
+ '-webkit-app-region',
+ '-webkit-appearance',
+ '-webkit-appearance',
+ '-moz-appearance',
+ '-webkit-aspect-ratio',
+ '-webkit-backdrop-filter',
+ 'backface-visibility',
+ '-webkit-backface-visibility',
+ 'backface-visibility',
+ 'backface-visibility',
+ '-webkit-background-composite',
+ '-webkit-background-composite',
+ '-moz-binding',
+ '-ms-block-progression',
+ '-webkit-border-after',
+ '-webkit-border-after',
+ '-webkit-border-after-color',
+ '-webkit-border-after-color',
+ '-webkit-border-after-style',
+ '-webkit-border-after-style',
+ '-webkit-border-after-width',
+ '-webkit-border-after-width',
+ '-webkit-border-before',
+ '-webkit-border-before',
+ '-webkit-border-before-color',
+ '-webkit-border-before-color',
+ '-webkit-border-before-style',
+ '-webkit-border-before-style',
+ '-webkit-border-before-width',
+ '-webkit-border-before-width',
+ '-moz-border-bottom-colors',
+ '-webkit-border-end',
+ '-webkit-border-end',
+ '-moz-border-end',
+ '-webkit-border-end-color',
+ '-webkit-border-end-color',
+ '-moz-border-end-color',
+ '-webkit-border-end-style',
+ '-webkit-border-end-style',
+ '-moz-border-end-style',
+ '-webkit-border-end-width',
+ '-webkit-border-end-width',
+ '-moz-border-end-width',
+ '-webkit-border-fit',
+ '-webkit-border-horizontal-spacing',
+ '-webkit-border-horizontal-spacing',
+ '-moz-border-left-colors',
+ '-moz-border-right-colors',
+ '-webkit-border-start',
+ '-webkit-border-start',
+ '-moz-border-start',
+ '-webkit-border-start-color',
+ '-webkit-border-start-color',
+ '-moz-border-start-color',
+ '-webkit-border-start-style',
+ '-webkit-border-start-style',
+ '-moz-border-start-style',
+ '-webkit-border-start-width',
+ '-webkit-border-start-width',
+ '-moz-border-start-width',
+ '-moz-border-top-colors',
+ '-webkit-border-vertical-spacing',
+ '-webkit-border-vertical-spacing',
+ '-webkit-box-align',
+ '-webkit-box-align',
+ '-moz-box-align',
+ '-webkit-box-decoration-break',
+ '-webkit-box-decoration-break',
+ 'box-decoration-break',
+ '-webkit-box-direction',
+ '-webkit-box-direction',
+ '-moz-box-direction',
+ '-webkit-box-flex',
+ '-webkit-box-flex',
+ '-moz-box-flex',
+ '-webkit-box-flex-group',
+ '-webkit-box-flex-group',
+ '-webkit-box-lines',
+ '-webkit-box-lines',
+ '-webkit-box-ordinal-group',
+ '-webkit-box-ordinal-group',
+ '-moz-box-ordinal-group',
+ '-webkit-box-orient',
+ '-webkit-box-orient',
+ '-moz-box-orient',
+ '-webkit-box-pack',
+ '-webkit-box-pack',
+ '-moz-box-pack',
+ '-webkit-box-reflect',
+ '-webkit-box-reflect',
+ 'clip-path',
+ '-webkit-clip-path',
+ 'clip-path',
+ 'clip-path',
+ '-webkit-color-correction',
+ '-webkit-column-axis',
+ '-webkit-column-break-after',
+ '-webkit-column-break-after',
+ '-webkit-column-break-before',
+ '-webkit-column-break-before',
+ '-webkit-column-break-inside',
+ '-webkit-column-break-inside',
+ '-webkit-column-count',
+ 'column-count',
+ '-moz-column-count',
+ 'column-count',
+ 'column-fill',
+ 'column-fill',
+ '-moz-column-fill',
+ 'column-fill',
+ '-webkit-column-gap',
+ 'column-gap',
+ '-moz-column-gap',
+ 'column-gap',
+ '-webkit-column-rule',
+ 'column-rule',
+ '-moz-column-rule',
+ 'column-rule',
+ '-webkit-column-rule-color',
+ 'column-rule-color',
+ '-moz-column-rule-color',
+ 'column-rule-color',
+ '-webkit-column-rule-style',
+ 'column-rule-style',
+ '-moz-column-rule-style',
+ 'column-rule-style',
+ '-webkit-column-rule-width',
+ 'column-rule-width',
+ '-moz-column-rule-width',
+ 'column-rule-width',
+ '-webkit-column-span',
+ 'column-span',
+ 'column-span',
+ '-webkit-column-width',
+ 'column-width',
+ '-moz-column-width',
+ 'column-width',
+ '-webkit-columns',
+ 'columns',
+ '-moz-columns',
+ 'columns',
+ '-ms-content-zoom-chaining',
+ '-ms-content-zoom-limit',
+ '-ms-content-zoom-limit-max',
+ '-ms-content-zoom-limit-min',
+ '-ms-content-zoom-snap',
+ '-ms-content-zoom-snap-points',
+ '-ms-content-zoom-snap-type',
+ '-ms-content-zooming',
+ '-moz-control-character-visibility',
+ '-webkit-cursor-visibility',
+ '-webkit-dashboard-region',
+ 'filter',
+ '-webkit-filter',
+ 'filter',
+ 'filter',
+ '-ms-flex-align',
+ '-ms-flex-item-align',
+ '-ms-flex-line-pack',
+ '-ms-flex-negative',
+ '-ms-flex-order',
+ '-ms-flex-pack',
+ '-ms-flex-positive',
+ '-ms-flex-preferred-size',
+ '-moz-float-edge',
+ '-webkit-flow-from',
+ '-ms-flow-from',
+ '-webkit-flow-into',
+ '-ms-flow-into',
+ '-webkit-font-feature-settings',
+ '-webkit-font-feature-settings',
+ 'font-feature-settings',
+ 'font-feature-settings',
+ 'font-kerning',
+ '-webkit-font-kerning',
+ 'font-kerning',
+ '-webkit-font-size-delta',
+ '-webkit-font-size-delta',
+ '-webkit-font-smoothing',
+ '-webkit-font-smoothing',
+ 'font-variant-ligatures',
+ '-webkit-font-variant-ligatures',
+ 'font-variant-ligatures',
+ '-moz-force-broken-image-icon',
+ 'grid',
+ '-webkit-grid',
+ 'grid',
+ 'grid-area',
+ '-webkit-grid-area',
+ 'grid-area',
+ 'grid-auto-columns',
+ '-webkit-grid-auto-columns',
+ 'grid-auto-columns',
+ 'grid-auto-flow',
+ '-webkit-grid-auto-flow',
+ 'grid-auto-flow',
+ 'grid-auto-rows',
+ '-webkit-grid-auto-rows',
+ 'grid-auto-rows',
+ 'grid-column',
+ '-webkit-grid-column',
+ 'grid-column',
+ '-ms-grid-column',
+ '-ms-grid-column-align',
+ 'grid-column-end',
+ '-webkit-grid-column-end',
+ 'grid-column-end',
+ '-ms-grid-column-span',
+ 'grid-column-start',
+ '-webkit-grid-column-start',
+ 'grid-column-start',
+ '-ms-grid-columns',
+ 'grid-row',
+ '-webkit-grid-row',
+ 'grid-row',
+ '-ms-grid-row',
+ '-ms-grid-row-align',
+ 'grid-row-end',
+ '-webkit-grid-row-end',
+ 'grid-row-end',
+ '-ms-grid-row-span',
+ 'grid-row-start',
+ '-webkit-grid-row-start',
+ 'grid-row-start',
+ '-ms-grid-rows',
+ 'grid-template',
+ '-webkit-grid-template',
+ 'grid-template',
+ 'grid-template-areas',
+ '-webkit-grid-template-areas',
+ 'grid-template-areas',
+ 'grid-template-columns',
+ '-webkit-grid-template-columns',
+ 'grid-template-columns',
+ 'grid-template-rows',
+ '-webkit-grid-template-rows',
+ 'grid-template-rows',
+ '-ms-high-contrast-adjust',
+ '-webkit-highlight',
+ '-webkit-hyphenate-character',
+ '-webkit-hyphenate-character',
+ '-webkit-hyphenate-limit-after',
+ '-webkit-hyphenate-limit-before',
+ '-ms-hyphenate-limit-chars',
+ '-webkit-hyphenate-limit-lines',
+ '-ms-hyphenate-limit-lines',
+ '-ms-hyphenate-limit-zone',
+ '-webkit-hyphens',
+ '-moz-hyphens',
+ '-ms-hyphens',
+ '-moz-image-region',
+ '-ms-ime-align',
+ '-webkit-initial-letter',
+ '-ms-interpolation-mode',
+ 'justify-self',
+ '-webkit-justify-self',
+ '-webkit-line-align',
+ '-webkit-line-box-contain',
+ '-webkit-line-box-contain',
+ '-webkit-line-break',
+ '-webkit-line-break',
+ 'line-break',
+ '-webkit-line-clamp',
+ '-webkit-line-clamp',
+ '-webkit-line-grid',
+ '-webkit-line-snap',
+ '-webkit-locale',
+ '-webkit-locale',
+ '-webkit-logical-height',
+ '-webkit-logical-height',
+ '-webkit-logical-width',
+ '-webkit-logical-width',
+ '-webkit-margin-after',
+ '-webkit-margin-after',
+ '-webkit-margin-after-collapse',
+ '-webkit-margin-after-collapse',
+ '-webkit-margin-before',
+ '-webkit-margin-before',
+ '-webkit-margin-before-collapse',
+ '-webkit-margin-before-collapse',
+ '-webkit-margin-bottom-collapse',
+ '-webkit-margin-bottom-collapse',
+ '-webkit-margin-collapse',
+ '-webkit-margin-collapse',
+ '-webkit-margin-end',
+ '-webkit-margin-end',
+ '-moz-margin-end',
+ '-webkit-margin-start',
+ '-webkit-margin-start',
+ '-moz-margin-start',
+ '-webkit-margin-top-collapse',
+ '-webkit-margin-top-collapse',
+ '-webkit-marquee',
+ '-webkit-marquee-direction',
+ '-webkit-marquee-increment',
+ '-webkit-marquee-repetition',
+ '-webkit-marquee-speed',
+ '-webkit-marquee-style',
+ 'mask',
+ '-webkit-mask',
+ 'mask',
+ '-webkit-mask-box-image',
+ '-webkit-mask-box-image',
+ '-webkit-mask-box-image-outset',
+ '-webkit-mask-box-image-outset',
+ '-webkit-mask-box-image-repeat',
+ '-webkit-mask-box-image-repeat',
+ '-webkit-mask-box-image-slice',
+ '-webkit-mask-box-image-slice',
+ '-webkit-mask-box-image-source',
+ '-webkit-mask-box-image-source',
+ '-webkit-mask-box-image-width',
+ '-webkit-mask-box-image-width',
+ '-webkit-mask-clip',
+ '-webkit-mask-clip',
+ '-webkit-mask-composite',
+ '-webkit-mask-composite',
+ '-webkit-mask-image',
+ '-webkit-mask-image',
+ '-webkit-mask-origin',
+ '-webkit-mask-origin',
+ '-webkit-mask-position',
+ '-webkit-mask-position',
+ '-webkit-mask-position-x',
+ '-webkit-mask-position-x',
+ '-webkit-mask-position-y',
+ '-webkit-mask-position-y',
+ '-webkit-mask-repeat',
+ '-webkit-mask-repeat',
+ '-webkit-mask-repeat-x',
+ '-webkit-mask-repeat-x',
+ '-webkit-mask-repeat-y',
+ '-webkit-mask-repeat-y',
+ '-webkit-mask-size',
+ '-webkit-mask-size',
+ 'mask-source-type',
+ '-webkit-mask-source-type',
+ '-moz-math-display',
+ '-moz-math-variant',
+ '-webkit-max-logical-height',
+ '-webkit-max-logical-height',
+ '-webkit-max-logical-width',
+ '-webkit-max-logical-width',
+ '-webkit-min-logical-height',
+ '-webkit-min-logical-height',
+ '-webkit-min-logical-width',
+ '-webkit-min-logical-width',
+ '-webkit-nbsp-mode',
+ '-moz-orient',
+ '-moz-osx-font-smoothing',
+ '-moz-outline-radius',
+ '-moz-outline-radius-bottomleft',
+ '-moz-outline-radius-bottomright',
+ '-moz-outline-radius-topleft',
+ '-moz-outline-radius-topright',
+ '-webkit-overflow-scrolling',
+ '-ms-overflow-style',
+ '-webkit-padding-after',
+ '-webkit-padding-after',
+ '-webkit-padding-before',
+ '-webkit-padding-before',
+ '-webkit-padding-end',
+ '-webkit-padding-end',
+ '-moz-padding-end',
+ '-webkit-padding-start',
+ '-webkit-padding-start',
+ '-moz-padding-start',
+ 'perspective',
+ '-webkit-perspective',
+ 'perspective',
+ 'perspective',
+ 'perspective-origin',
+ '-webkit-perspective-origin',
+ 'perspective-origin',
+ 'perspective-origin',
+ '-webkit-perspective-origin-x',
+ '-webkit-perspective-origin-x',
+ 'perspective-origin-x',
+ '-webkit-perspective-origin-y',
+ '-webkit-perspective-origin-y',
+ 'perspective-origin-y',
+ '-webkit-print-color-adjust',
+ '-webkit-print-color-adjust',
+ '-webkit-region-break-after',
+ '-webkit-region-break-before',
+ '-webkit-region-break-inside',
+ '-webkit-region-fragment',
+ '-webkit-rtl-ordering',
+ '-webkit-rtl-ordering',
+ '-webkit-ruby-position',
+ '-webkit-ruby-position',
+ 'ruby-position',
+ '-moz-script-level',
+ '-moz-script-min-size',
+ '-moz-script-size-multiplier',
+ '-ms-scroll-chaining',
+ '-ms-scroll-limit',
+ '-ms-scroll-limit-x-max',
+ '-ms-scroll-limit-x-min',
+ '-ms-scroll-limit-y-max',
+ '-ms-scroll-limit-y-min',
+ '-ms-scroll-rails',
+ '-webkit-scroll-snap-coordinate',
+ '-webkit-scroll-snap-destination',
+ '-webkit-scroll-snap-points-x',
+ '-ms-scroll-snap-points-x',
+ '-webkit-scroll-snap-points-y',
+ '-ms-scroll-snap-points-y',
+ '-webkit-scroll-snap-type',
+ '-ms-scroll-snap-type',
+ '-ms-scroll-snap-x',
+ '-ms-scroll-snap-y',
+ '-ms-scroll-translation',
+ '-ms-scrollbar-3dlight-color',
+ 'shape-image-threshold',
+ '-webkit-shape-image-threshold',
+ 'shape-margin',
+ '-webkit-shape-margin',
+ 'shape-outside',
+ '-webkit-shape-outside',
+ '-moz-stack-sizing',
+ 'tab-size',
+ 'tab-size',
+ '-moz-tab-size',
+ '-webkit-tap-highlight-color',
+ '-webkit-tap-highlight-color',
+ 'text-align-last',
+ '-webkit-text-align-last',
+ '-moz-text-align-last',
+ 'text-align-last',
+ '-webkit-text-combine',
+ '-webkit-text-combine',
+ '-ms-text-combine-horizontal',
+ 'text-decoration-color',
+ '-webkit-text-decoration-color',
+ 'text-decoration-color',
+ 'text-decoration-color',
+ 'text-decoration-line',
+ '-webkit-text-decoration-line',
+ 'text-decoration-line',
+ '-webkit-text-decoration-skip',
+ 'text-decoration-style',
+ '-webkit-text-decoration-style',
+ 'text-decoration-style',
+ '-webkit-text-decorations-in-effect',
+ '-webkit-text-decorations-in-effect',
+ '-webkit-text-emphasis',
+ 'text-emphasis',
+ '-webkit-text-emphasis-color',
+ 'text-emphasis-color',
+ '-webkit-text-emphasis-position',
+ 'text-emphasis-position',
+ '-webkit-text-emphasis-style',
+ 'text-emphasis-style',
+ '-webkit-text-fill-color',
+ '-webkit-text-fill-color',
+ 'text-justify',
+ '-webkit-text-justify',
+ 'text-justify',
+ '-webkit-text-orientation',
+ '-webkit-text-orientation',
+ 'text-orientation',
+ '-webkit-text-security',
+ '-webkit-text-security',
+ '-webkit-text-size-adjust',
+ '-moz-text-size-adjust',
+ '-ms-text-size-adjust',
+ '-webkit-text-stroke',
+ '-webkit-text-stroke',
+ '-webkit-text-stroke-color',
+ '-webkit-text-stroke-color',
+ '-webkit-text-stroke-width',
+ '-webkit-text-stroke-width',
+ 'text-underline-position',
+ '-webkit-text-underline-position',
+ 'text-underline-position',
+ '-webkit-touch-callout',
+ '-ms-touch-select',
+ 'transform',
+ '-webkit-transform',
+ 'transform',
+ 'transform',
+ 'transform-origin',
+ '-webkit-transform-origin',
+ 'transform-origin',
+ 'transform-origin',
+ '-webkit-transform-origin-x',
+ '-webkit-transform-origin-x',
+ 'transform-origin-x',
+ '-webkit-transform-origin-y',
+ '-webkit-transform-origin-y',
+ 'transform-origin-y',
+ '-webkit-transform-origin-z',
+ '-webkit-transform-origin-z',
+ 'transform-origin-z',
+ 'transform-style',
+ '-webkit-transform-style',
+ 'transform-style',
+ 'transform-style',
+ '-webkit-user-drag',
+ '-webkit-user-drag',
+ '-moz-user-focus',
+ '-moz-user-input',
+ '-webkit-user-modify',
+ '-webkit-user-modify',
+ '-moz-user-modify',
+ '-webkit-user-select',
+ '-webkit-user-select',
+ '-moz-user-select',
+ '-ms-user-select',
+ '-moz-window-dragging',
+ '-moz-window-shadow',
+ '-ms-wrap-flow',
+ '-ms-wrap-margin',
+ '-ms-wrap-through',
+ 'writing-mode',
+ '-webkit-writing-mode',
+ 'writing-mode',
+ 'writing-mode',
]
all_styles = standard_styles + all_prefixed_styles
mathml_tags = [
- 'abs', 'and', 'annotation', 'annotation-xml', 'apply', 'approx', 'arccos', 'arccosh', 'arccot', 'arccoth', 'arccsc',
- 'arccsch', 'arcsec', 'arcsech', 'arcsin', 'arcsinh', 'arctan', 'arctanh', 'arg', 'bind', 'bvar', 'card',
- 'cartesianproduct', 'cbytes', 'ceiling', 'cerror', 'ci', 'cn', 'codomain', 'complexes', 'compose', 'condition',
- 'conjugate', 'cos', 'cosh', 'cot', 'coth', 'cs', 'csc', 'csch', 'csymbol', 'curl', 'declare', 'degree',
- 'determinant', 'diff', 'divergence', 'divide', 'domain', 'domainofapplication', 'emptyset', 'encoding', 'eq',
- 'equivalent', 'eulergamma', 'exists', 'exp', 'exponentiale', 'factorial', 'factorof', 'false', 'floor', 'fn',
- 'forall', 'function', 'gcd', 'geq', 'grad', 'gt', 'ident', 'image', 'imaginary', 'imaginaryi', 'implies', 'in',
- 'infinity', 'int', 'integers', 'intersect', 'interval', 'inverse', 'lambda', 'laplacian', 'lcm', 'leq', 'limit',
- 'list', 'ln', 'log', 'logbase', 'lowlimit', 'lt', 'maction', 'malign', 'maligngroup', 'malignmark', 'malignscope',
- 'math', 'matrix', 'matrixrow', 'max', 'mean', 'median', 'menclose', 'merror', 'mfenced', 'mfrac', 'mfraction',
- 'mglyph', 'mi', 'min', 'minus', 'mlabeledtr', 'mlongdiv', 'mmultiscripts', 'mn', 'mo', 'mode', 'moment',
- 'momentabout', 'mover', 'mpadded', 'mphantom', 'mprescripts', 'mroot', 'mrow', 'ms', 'mscarries', 'mscarry',
- 'msgroup', 'msline', 'mspace', 'msqrt', 'msrow', 'mstack', 'mstyle', 'msub', 'msubsup', 'msup', 'mtable', 'mtd',
- 'mtext', 'mtr', 'munder', 'munderover', 'naturalnumbers', 'neq', 'none', 'not', 'notanumber', 'notin',
- 'notprsubset', 'notsubset', 'or', 'otherwise', 'outerproduct', 'partialdiff', 'pi', 'piece', 'piecewice',
- 'piecewise', 'plus', 'power', 'primes', 'product', 'prsubset', 'quotient', 'rationals', 'real', 'reals', 'reln',
- 'rem', 'root', 'scalarproduct', 'sdev', 'sec', 'sech', 'select', 'selector', 'semantics', 'sep', 'set', 'setdiff',
- 'share', 'sin', 'sinh', 'span', 'subset', 'sum', 'tan', 'tanh', 'tendsto', 'times', 'transpose', 'true', 'union',
- 'uplimit', 'var', 'variance', 'vector', 'vectorproduct', 'xor',
+ 'abs',
+ 'and',
+ 'annotation',
+ 'annotation-xml',
+ 'apply',
+ 'approx',
+ 'arccos',
+ 'arccosh',
+ 'arccot',
+ 'arccoth',
+ 'arccsc',
+ 'arccsch',
+ 'arcsec',
+ 'arcsech',
+ 'arcsin',
+ 'arcsinh',
+ 'arctan',
+ 'arctanh',
+ 'arg',
+ 'bind',
+ 'bvar',
+ 'card',
+ 'cartesianproduct',
+ 'cbytes',
+ 'ceiling',
+ 'cerror',
+ 'ci',
+ 'cn',
+ 'codomain',
+ 'complexes',
+ 'compose',
+ 'condition',
+ 'conjugate',
+ 'cos',
+ 'cosh',
+ 'cot',
+ 'coth',
+ 'cs',
+ 'csc',
+ 'csch',
+ 'csymbol',
+ 'curl',
+ 'declare',
+ 'degree',
+ 'determinant',
+ 'diff',
+ 'divergence',
+ 'divide',
+ 'domain',
+ 'domainofapplication',
+ 'emptyset',
+ 'encoding',
+ 'eq',
+ 'equivalent',
+ 'eulergamma',
+ 'exists',
+ 'exp',
+ 'exponentiale',
+ 'factorial',
+ 'factorof',
+ 'false',
+ 'floor',
+ 'fn',
+ 'forall',
+ 'function',
+ 'gcd',
+ 'geq',
+ 'grad',
+ 'gt',
+ 'ident',
+ 'image',
+ 'imaginary',
+ 'imaginaryi',
+ 'implies',
+ 'in',
+ 'infinity',
+ 'int',
+ 'integers',
+ 'intersect',
+ 'interval',
+ 'inverse',
+ 'lambda',
+ 'laplacian',
+ 'lcm',
+ 'leq',
+ 'limit',
+ 'list',
+ 'ln',
+ 'log',
+ 'logbase',
+ 'lowlimit',
+ 'lt',
+ 'maction',
+ 'malign',
+ 'maligngroup',
+ 'malignmark',
+ 'malignscope',
+ 'math',
+ 'matrix',
+ 'matrixrow',
+ 'max',
+ 'mean',
+ 'median',
+ 'menclose',
+ 'merror',
+ 'mfenced',
+ 'mfrac',
+ 'mfraction',
+ 'mglyph',
+ 'mi',
+ 'min',
+ 'minus',
+ 'mlabeledtr',
+ 'mlongdiv',
+ 'mmultiscripts',
+ 'mn',
+ 'mo',
+ 'mode',
+ 'moment',
+ 'momentabout',
+ 'mover',
+ 'mpadded',
+ 'mphantom',
+ 'mprescripts',
+ 'mroot',
+ 'mrow',
+ 'ms',
+ 'mscarries',
+ 'mscarry',
+ 'msgroup',
+ 'msline',
+ 'mspace',
+ 'msqrt',
+ 'msrow',
+ 'mstack',
+ 'mstyle',
+ 'msub',
+ 'msubsup',
+ 'msup',
+ 'mtable',
+ 'mtd',
+ 'mtext',
+ 'mtr',
+ 'munder',
+ 'munderover',
+ 'naturalnumbers',
+ 'neq',
+ 'none',
+ 'not',
+ 'notanumber',
+ 'notin',
+ 'notprsubset',
+ 'notsubset',
+ 'or',
+ 'otherwise',
+ 'outerproduct',
+ 'partialdiff',
+ 'pi',
+ 'piece',
+ 'piecewice',
+ 'piecewise',
+ 'plus',
+ 'power',
+ 'primes',
+ 'product',
+ 'prsubset',
+ 'quotient',
+ 'rationals',
+ 'real',
+ 'reals',
+ 'reln',
+ 'rem',
+ 'root',
+ 'scalarproduct',
+ 'sdev',
+ 'sec',
+ 'sech',
+ 'select',
+ 'selector',
+ 'semantics',
+ 'sep',
+ 'set',
+ 'setdiff',
+ 'share',
+ 'sin',
+ 'sinh',
+ 'span',
+ 'subset',
+ 'sum',
+ 'tan',
+ 'tanh',
+ 'tendsto',
+ 'times',
+ 'transpose',
+ 'true',
+ 'union',
+ 'uplimit',
+ 'var',
+ 'variance',
+ 'vector',
+ 'vectorproduct',
+ 'xor',
]
mathml_attrs = {
- 'mo': ['accent', 'dir', 'fence', 'form', 'indentalign', 'indentalignfirst', 'indentalignlast', 'indentshift',
- 'indentshiftfirst', 'indentshiftlast', 'indenttarget', 'largeop', 'linebreak', 'linebreakmultchar',
- 'linebreakstyle', 'lineleading', 'lspace', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits',
- 'rspace', 'separator', 'stretchy', 'symmetric', 'href', 'id', 'mathbackground', 'mathcolor'],
+ 'mo': [
+ 'accent',
+ 'dir',
+ 'fence',
+ 'form',
+ 'indentalign',
+ 'indentalignfirst',
+ 'indentalignlast',
+ 'indentshift',
+ 'indentshiftfirst',
+ 'indentshiftlast',
+ 'indenttarget',
+ 'largeop',
+ 'linebreak',
+ 'linebreakmultchar',
+ 'linebreakstyle',
+ 'lineleading',
+ 'lspace',
+ 'mathsize',
+ 'mathvariant',
+ 'maxsize',
+ 'minsize',
+ 'movablelimits',
+ 'rspace',
+ 'separator',
+ 'stretchy',
+ 'symmetric',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
'mover': ['accent', 'align', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'munderover': ['accent', 'accentunder', 'align', 'href', 'id', 'mathbackground', 'mathcolor'],
+ 'munderover': [
+ 'accent',
+ 'accentunder',
+ 'align',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
'munder': ['accentunder', 'align', 'href', 'id', 'mathbackground', 'mathcolor'],
'maction': ['actiontype', 'selection', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mtable': ['align', 'alignmentscope', 'columnalign', 'columnlines', 'columnspacing', 'columnwidth', 'displaystyle',
- 'equalcolumns', 'equalrows', 'frame', 'framespacing', 'groupalign', 'minlabelspacing', 'rowalign',
- 'rowlines', 'rowspacing', 'side', 'width', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mstack': ['align', 'charalign', 'stackalign', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'math': ['altimg', 'altimg-width', 'altimg-height', 'altimg-valign', 'alttext', 'dir', 'display', 'overflow',
- 'xmlns', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mfrac': ['bevelled', 'denomalign', 'linethickness', 'numalign', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mfenced': ['close', 'open', 'separators', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mtd': ['columnalign', 'columnspan', 'groupalign', 'rowalign', 'rowspan', 'href', 'id', 'mathbackground',
- 'mathcolor'],
- 'mtr': ['columnalign', 'groupalign', 'rowalign', 'href', 'id', 'mathbackground', 'mathcolor'],
+ 'mtable': [
+ 'align',
+ 'alignmentscope',
+ 'columnalign',
+ 'columnlines',
+ 'columnspacing',
+ 'columnwidth',
+ 'displaystyle',
+ 'equalcolumns',
+ 'equalrows',
+ 'frame',
+ 'framespacing',
+ 'groupalign',
+ 'minlabelspacing',
+ 'rowalign',
+ 'rowlines',
+ 'rowspacing',
+ 'side',
+ 'width',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
+ 'mstack': [
+ 'align',
+ 'charalign',
+ 'stackalign',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
+ 'math': [
+ 'altimg',
+ 'altimg-width',
+ 'altimg-height',
+ 'altimg-valign',
+ 'alttext',
+ 'dir',
+ 'display',
+ 'overflow',
+ 'xmlns',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
+ 'mfrac': [
+ 'bevelled',
+ 'denomalign',
+ 'linethickness',
+ 'numalign',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
+ 'mfenced': [
+ 'close',
+ 'open',
+ 'separators',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
+ 'mtd': [
+ 'columnalign',
+ 'columnspan',
+ 'groupalign',
+ 'rowalign',
+ 'rowspan',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
+ 'mtr': [
+ 'columnalign',
+ 'groupalign',
+ 'rowalign',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
'mlabeledtr': ['columnalign', 'href', 'id', 'mathbackground', 'mathcolor'],
'mscarry': ['crossout', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mstyle': ['decimalpoint', 'displaystyle', 'infixlinebreakstyle', 'scriptlevel', 'scriptminsize',
- 'scriptsizemultiplier', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mpadded': ['depth', 'height', 'lspace', 'voffset', 'width', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mi': ['dir', 'mathsize', 'mathvariant', 'href', 'id', 'mathbackground', 'mathcolor'],
+ 'mstyle': [
+ 'decimalpoint',
+ 'displaystyle',
+ 'infixlinebreakstyle',
+ 'scriptlevel',
+ 'scriptminsize',
+ 'scriptsizemultiplier',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
+ 'mpadded': [
+ 'depth',
+ 'height',
+ 'lspace',
+ 'voffset',
+ 'width',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
+ 'mi': [
+ 'dir',
+ 'mathsize',
+ 'mathvariant',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
'mrow': ['dir', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'ms': ['dir', 'lquote', 'mathsize', 'mathvariant', 'rquote', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mtext': ['dir', 'mathsize', 'mathvariant', 'href', 'id', 'mathbackground', 'mathcolor'],
+ 'ms': [
+ 'dir',
+ 'lquote',
+ 'mathsize',
+ 'mathvariant',
+ 'rquote',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
+ 'mtext': [
+ 'dir',
+ 'mathsize',
+ 'mathvariant',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
'malignmark': ['edge', 'href', 'id', 'mathbackground', 'mathcolor'],
'maligngroup': ['groupalign', 'href', 'id', 'mathbackground', 'mathcolor'],
'mglyph': ['height', 'src', 'width', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mspace': ['height', 'indentalign', 'indentalignfirst', 'indentalignlast', 'indentshift', 'indentshiftfirst',
- 'indentshiftlast', 'indenttarget', 'linebreak', 'linebreakmultchar', 'linebreakstyle', 'lineleading',
- 'width', 'href', 'id', 'mathbackground', 'mathcolor'],
+ 'mspace': [
+ 'height',
+ 'indentalign',
+ 'indentalignfirst',
+ 'indentalignlast',
+ 'indentshift',
+ 'indentshiftfirst',
+ 'indentshiftlast',
+ 'indenttarget',
+ 'linebreak',
+ 'linebreakmultchar',
+ 'linebreakstyle',
+ 'lineleading',
+ 'width',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
'msline': ['length', 'position', 'href', 'id', 'mathbackground', 'mathcolor'],
'mscarries': ['location', 'position', 'href', 'id', 'mathbackground', 'mathcolor'],
'mlongdiv': ['longdivstyle', 'href', 'id', 'mathbackground', 'mathcolor'],
@@ -353,9 +1364,23 @@
'menclose': ['notation', 'href', 'id', 'mathbackground', 'mathcolor'],
'msgroup': ['position', 'shift', 'href', 'id', 'mathbackground', 'mathcolor'],
'msrow': ['position', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'mmultiscripts': ['subscriptshift', 'supscriptshift', 'href', 'id', 'mathbackground', 'mathcolor'],
+ 'mmultiscripts': [
+ 'subscriptshift',
+ 'supscriptshift',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
'msub': ['subscriptshift', 'href', 'id', 'mathbackground', 'mathcolor'],
- 'msubsup': ['subscriptshift', 'supscriptshift', 'href', 'id', 'mathbackground', 'mathcolor'],
+ 'msubsup': [
+ 'subscriptshift',
+ 'supscriptshift',
+ 'href',
+ 'id',
+ 'mathbackground',
+ 'mathcolor',
+ ],
'msup': ['supscriptshift', 'href', 'id', 'mathbackground', 'mathcolor'],
'abs': ['href', 'id', 'mathbackground', 'mathcolor'],
'and': ['href', 'id', 'mathbackground', 'mathcolor'],
diff --git a/judge/jinja2/markdown/math.py b/judge/jinja2/markdown/math.py
index d619d5bd83..5ddc0881fa 100644
--- a/judge/jinja2/markdown/math.py
+++ b/judge/jinja2/markdown/math.py
@@ -20,7 +20,9 @@ def __init__(self, *args, **kwargs):
self.default_rules = self.default_rules[:]
self.inline_html_rules = self.default_rules
self.default_rules.insert(self.default_rules.index('strikethrough') + 1, 'math')
- self.default_rules.insert(self.default_rules.index('strikethrough') + 1, 'block_math')
+ self.default_rules.insert(
+ self.default_rules.index('strikethrough') + 1, 'block_math'
+ )
super(MathInlineLexer, self).__init__(*args, **kwargs)
def output_block_math(self, m):
diff --git a/judge/jinja2/markdown/test_markdown.py b/judge/jinja2/markdown/test_markdown.py
index 689ca54f3e..6cee9a0f63 100644
--- a/judge/jinja2/markdown/test_markdown.py
+++ b/judge/jinja2/markdown/test_markdown.py
@@ -109,12 +109,20 @@ class TestMarkdown(SimpleTestCase):
UNBLEACHED_STYLE = 'problem-full'
def test_bleach(self):
- self.assertHTMLEqual(markdown('', self.BLEACHED_STYLE),
- '<script>void(0)</script>')
- self.assertHTMLEqual(markdown(' ', self.BLEACHED_STYLE),
- '
')
- self.assertHTMLEqual(markdown('', self.BLEACHED_STYLE),
- '')
+ self.assertHTMLEqual(
+ markdown('', self.BLEACHED_STYLE),
+ '<script>void(0)</script>',
+ )
+ self.assertHTMLEqual(
+ markdown(
+ ' ', self.BLEACHED_STYLE
+ ),
+ '
',
+ )
+ self.assertHTMLEqual(
+ markdown('', self.BLEACHED_STYLE),
+ '',
+ )
def test_bleach_mathml(self):
self.assertHTMLEqual(markdown(MATHML_N, self.BLEACHED_STYLE), MATHML_N)
@@ -122,13 +130,17 @@ def test_bleach_mathml(self):
self.assertHTMLEqual(cleaner.clean(MATHML_CHUDNOVSKY), MATHML_CHUDNOVSKY)
def test_no_bleach(self):
- self.assertHTMLEqual(markdown('', self.UNBLEACHED_STYLE),
- '')
+ self.assertHTMLEqual(
+ markdown('', self.UNBLEACHED_STYLE),
+ '',
+ )
def test_post_process(self):
- self.assertHTMLEqual(markdown(' ', self.UNBLEACHED_STYLE, lazy_load=True),
- ' '
- '
')
+ self.assertHTMLEqual(
+ markdown(' ', self.UNBLEACHED_STYLE, lazy_load=True),
+ ' '
+ '
',
+ )
class TestFragmentUtils(SimpleTestCase):
diff --git a/judge/jinja2/reference.py b/judge/jinja2/reference.py
index 5dd0c84a12..1bc100924a 100644
--- a/judge/jinja2/reference.py
+++ b/judge/jinja2/reference.py
@@ -37,11 +37,15 @@ def get_user_rating(username, data):
return element
rating = data[1]
- element = Element('a', {'class': 'rate-group', 'href': reverse('user_page', args=[username])})
+ element = Element(
+ 'a', {'class': 'rate-group', 'href': reverse('user_page', args=[username])}
+ )
if rating:
rating_css = rating_class(rating)
rate_box = Element('span', {'class': 'rate-box ' + rating_css})
- rate_box.append(Element('span', {'style': 'height: %3.fem' % rating_progress(rating)}))
+ rate_box.append(
+ Element('span', {'style': 'height: %3.fem' % rating_progress(rating)})
+ )
user = Element('span', {'class': 'rating ' + rating_css})
user.text = username
element.append(rate_box)
@@ -52,9 +56,12 @@ def get_user_rating(username, data):
def get_user_info(usernames):
- return {name: (rank, rating) for name, rank, rating in
- Profile.objects.filter(user__username__in=usernames)
- .values_list('user__username', 'display_rank', 'rating')}
+ return {
+ name: (rank, rating)
+ for name, rank, rating in Profile.objects.filter(
+ user__username__in=usernames
+ ).values_list('user__username', 'display_rank', 'rating')
+ }
reference_map = {
@@ -71,9 +78,9 @@ def process_reference(text):
elements = []
for piece in rereference.finditer(text):
if prev is None:
- tail = text[last:piece.start()]
+ tail = text[last : piece.start()]
else:
- prev.append(text[last:piece.start()])
+ prev.append(text[last : piece.start()])
prev = list(piece.groups())
elements.append(prev)
last = piece.end()
@@ -150,9 +157,11 @@ def link_user(user):
user, profile = user.user, user
else:
raise ValueError('Expected profile or user, got %s' % (type(user),))
- return mark_safe(f''
- f''
- f'{escape(profile.display_name)} ')
+ return mark_safe(
+ f''
+ f''
+ f'{escape(profile.display_name)} '
+ )
@registry.function
diff --git a/judge/jinja2/registry.py b/judge/jinja2/registry.py
index da121666f9..12e4253486 100644
--- a/judge/jinja2/registry.py
+++ b/judge/jinja2/registry.py
@@ -16,6 +16,7 @@ def _store_function(store, func, name=None):
def _register_function(store, name, func):
if name is None and func is None:
+
def decorator(func):
_store_function(store, func)
return func
@@ -26,6 +27,7 @@ def decorator(func):
_store_function(store, name)
return name
else:
+
def decorator(func):
_store_function(store, func, name)
return func
diff --git a/judge/jinja2/render.py b/judge/jinja2/render.py
index 778e26ad6c..66d08f3acf 100644
--- a/judge/jinja2/render.py
+++ b/judge/jinja2/render.py
@@ -1,5 +1,9 @@
-from django.template import (Context, Template as DjangoTemplate, TemplateSyntaxError as DjangoTemplateSyntaxError,
- VariableDoesNotExist)
+from django.template import (
+ Context,
+ Template as DjangoTemplate,
+ TemplateSyntaxError as DjangoTemplateSyntaxError,
+ VariableDoesNotExist,
+)
from . import registry
diff --git a/judge/jinja2/social.py b/judge/jinja2/social.py
index 7a20ec3dd4..082f59c043 100644
--- a/judge/jinja2/social.py
+++ b/judge/jinja2/social.py
@@ -1,12 +1,23 @@
from django.template.loader import get_template
from django.utils.safestring import mark_safe
-from django_social_share.templatetags.social_share import post_to_facebook_url, post_to_twitter_url
+from django_social_share.templatetags.social_share import (
+ post_to_facebook_url,
+ post_to_twitter_url,
+)
from . import registry
SHARES = [
- ('post_to_twitter', 'django_social_share/templatetags/post_to_twitter.html', post_to_twitter_url),
- ('post_to_facebook', 'django_social_share/templatetags/post_to_facebook.html', post_to_facebook_url),
+ (
+ 'post_to_twitter',
+ 'django_social_share/templatetags/post_to_twitter.html',
+ post_to_twitter_url,
+ ),
+ (
+ 'post_to_facebook',
+ 'django_social_share/templatetags/post_to_facebook.html',
+ post_to_facebook_url,
+ ),
# Deprecated:
# ('post_to_gplus', 'django_social_share/templatetags/post_to_gplus.html', post_to_gplus_url),
# For future versions:
@@ -32,6 +43,10 @@ def func(request, *args):
@registry.function
def recaptcha_init(language=None):
- from snowpenguin.django.recaptcha2.templatetags.recaptcha2 import recaptcha_common_init
+ from snowpenguin.django.recaptcha2.templatetags.recaptcha2 import (
+ recaptcha_common_init,
+ )
+
return get_template('snowpenguin/recaptcha/recaptcha_init.html').render(
- recaptcha_common_init(language, {'explicit': False}))
+ recaptcha_common_init(language, {'explicit': False})
+ )
diff --git a/judge/jinja2/spaceless.py b/judge/jinja2/spaceless.py
index 90b6f36c3f..3bd1b97dcc 100644
--- a/judge/jinja2/spaceless.py
+++ b/judge/jinja2/spaceless.py
@@ -23,7 +23,9 @@ def parse(self, parser):
body = parser.parse_statements(['name:endspaceless'], drop_needle=True)
return nodes.CallBlock(
self.call_method('_strip_spaces', [], [], None, None),
- [], [], body,
+ [],
+ [],
+ body,
).set_lineno(lineno)
def _strip_spaces(self, caller=None):
diff --git a/judge/jinja2/submission.py b/judge/jinja2/submission.py
index 39dff8f963..3e49efcf3f 100644
--- a/judge/jinja2/submission.py
+++ b/judge/jinja2/submission.py
@@ -6,20 +6,32 @@
# TODO: maybe refactor this?
def get_editor_ids(contest):
- return set(map(attrgetter('id'), contest.authors.all())) | set(map(attrgetter('id'), contest.curators.all()))
+ return set(map(attrgetter('id'), contest.authors.all())) | set(
+ map(attrgetter('id'), contest.curators.all())
+ )
@registry.function
-def submission_layout(submission, profile_id, user, completed_problem_ids, editable_problem_ids, tester_problem_ids):
+def submission_layout(
+ submission,
+ profile_id,
+ user,
+ completed_problem_ids,
+ editable_problem_ids,
+ tester_problem_ids,
+):
problem_id = submission.problem_id
submission_source_visibility = submission.problem.submission_source_visibility
can_view = False
can_edit = False
- if (user.has_perm('judge.edit_all_problem') or
- (user.has_perm('judge.edit_public_problem') and submission.problem.is_public) or
- # We try to avoid evaluating this as much as possible to keep it lazy.
- problem_id in editable_problem_ids):
+ if (
+ user.has_perm('judge.edit_all_problem')
+ or (user.has_perm('judge.edit_public_problem') and submission.problem.is_public)
+ or
+ # We try to avoid evaluating this as much as possible to keep it lazy.
+ problem_id in editable_problem_ids
+ ):
can_view = True
can_edit = True
elif user.has_perm('judge.view_all_submission'):
@@ -28,7 +40,9 @@ def submission_layout(submission, profile_id, user, completed_problem_ids, edita
can_view = True
elif submission_source_visibility == SubmissionSourceAccess.ALWAYS:
can_view = True
- elif submission.contest_object is not None and profile_id in get_editor_ids(submission.contest_object):
+ elif submission.contest_object is not None and profile_id in get_editor_ids(
+ submission.contest_object
+ ):
can_view = True
elif submission.problem_id in completed_problem_ids:
can_view = submission.problem_id in tester_problem_ids
diff --git a/judge/judgeapi.py b/judge/judgeapi.py
index 25a34db3af..3b7163f239 100644
--- a/judge/judgeapi.py
+++ b/judge/judgeapi.py
@@ -8,7 +8,12 @@
from django.utils import timezone
from judge import event_poster as event
-from judge.judge_priority import BATCH_REJUDGE_PRIORITY, CONTEST_SUBMISSION_PRIORITY, DEFAULT_PRIORITY, REJUDGE_PRIORITY
+from judge.judge_priority import (
+ BATCH_REJUDGE_PRIORITY,
+ CONTEST_SUBMISSION_PRIORITY,
+ DEFAULT_PRIORITY,
+ REJUDGE_PRIORITY,
+)
logger = logging.getLogger('judge.judgeapi')
size_pack = struct.Struct('!I')
@@ -16,16 +21,24 @@
def _post_update_submission(submission, done=False):
if submission.problem.is_public:
- event.post('submissions', {'type': 'done-submission' if done else 'update-submission',
- 'id': submission.id,
- 'contest': submission.contest_key,
- 'user': submission.user_id, 'problem': submission.problem_id,
- 'status': submission.status, 'language': submission.language.key})
+ event.post(
+ 'submissions',
+ {
+ 'type': 'done-submission' if done else 'update-submission',
+ 'id': submission.id,
+ 'contest': submission.contest_key,
+ 'user': submission.user_id,
+ 'problem': submission.problem_id,
+ 'status': submission.status,
+ 'language': submission.language.key,
+ },
+ )
def judge_request(packet, reply=True):
- sock = socket.create_connection(settings.BRIDGED_DJANGO_CONNECT or
- settings.BRIDGED_DJANGO_ADDRESS[0])
+ sock = socket.create_connection(
+ settings.BRIDGED_DJANGO_CONNECT or settings.BRIDGED_DJANGO_ADDRESS[0]
+ )
output = json.dumps(packet, separators=(',', ':'))
output = zlib.compress(output.encode('utf-8'))
@@ -53,13 +66,25 @@ def judge_request(packet, reply=True):
def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=None):
from .models import ContestSubmission, Submission, SubmissionTestCase
- updates = {'time': None, 'memory': None, 'points': None, 'result': None, 'case_points': 0, 'case_total': 0,
- 'error': None, 'rejudged_date': timezone.now() if rejudge or batch_rejudge else None, 'status': 'QU'}
+ updates = {
+ 'time': None,
+ 'memory': None,
+ 'points': None,
+ 'result': None,
+ 'case_points': 0,
+ 'case_total': 0,
+ 'error': None,
+ 'rejudged_date': timezone.now() if rejudge or batch_rejudge else None,
+ 'status': 'QU',
+ }
try:
# This is set proactively; it might get unset in judgecallback's on_grading_begin if the problem doesn't
# actually have pretests stored on the judge.
- updates['is_pretested'] = all(ContestSubmission.objects.filter(submission=submission)
- .values_list('problem__contest__run_pretests_only', 'problem__is_pretested')[0])
+ updates['is_pretested'] = all(
+ ContestSubmission.objects.filter(submission=submission).values_list(
+ 'problem__contest__run_pretests_only', 'problem__is_pretested'
+ )[0]
+ )
except IndexError:
priority = DEFAULT_PRIORITY
else:
@@ -73,27 +98,38 @@ def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=No
# as that would prevent people from knowing a submission is being scheduled for rejudging.
# It is worth noting that this mechanism does not prevent a new rejudge from being scheduled
# while already queued, but that does not lead to data corruption.
- if not Submission.objects.filter(id=submission.id).exclude(status__in=('P', 'G')).update(**updates):
+ if (
+ not Submission.objects.filter(id=submission.id)
+ .exclude(status__in=('P', 'G'))
+ .update(**updates)
+ ):
return False
SubmissionTestCase.objects.filter(submission_id=submission.id).delete()
try:
- response = judge_request({
- 'name': 'submission-request',
- 'submission-id': submission.id,
- 'problem-id': submission.problem.code,
- 'language': submission.language.key,
- 'source': submission.source.source,
- 'judge-id': judge_id,
- 'priority': BATCH_REJUDGE_PRIORITY if batch_rejudge else (REJUDGE_PRIORITY if rejudge else priority),
- })
+ response = judge_request(
+ {
+ 'name': 'submission-request',
+ 'submission-id': submission.id,
+ 'problem-id': submission.problem.code,
+ 'language': submission.language.key,
+ 'source': submission.source.source,
+ 'judge-id': judge_id,
+ 'priority': BATCH_REJUDGE_PRIORITY
+ if batch_rejudge
+ else (REJUDGE_PRIORITY if rejudge else priority),
+ }
+ )
except BaseException:
logger.exception('Failed to send request to judge')
Submission.objects.filter(id=submission.id).update(status='IE', result='IE')
success = False
else:
- if response['name'] != 'submission-received' or response['submission-id'] != submission.id:
+ if (
+ response['name'] != 'submission-received'
+ or response['submission-id'] != submission.id
+ ):
Submission.objects.filter(id=submission.id).update(status='IE', result='IE')
_post_update_submission(submission)
success = True
@@ -101,23 +137,39 @@ def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=No
def disconnect_judge(judge, force=False):
- judge_request({'name': 'disconnect-judge', 'judge-id': judge.name, 'force': force}, reply=False)
+ judge_request(
+ {'name': 'disconnect-judge', 'judge-id': judge.name, 'force': force},
+ reply=False,
+ )
def update_disable_judge(judge):
- judge_request({'name': 'disable-judge', 'judge-id': judge.name, 'is-disabled': judge.is_disabled})
+ judge_request(
+ {
+ 'name': 'disable-judge',
+ 'judge-id': judge.name,
+ 'is-disabled': judge.is_disabled,
+ }
+ )
def abort_submission(submission):
from .models import Submission
+
# We only want to try to abort a submission if it's still grading, otherwise this can lead to fully graded
# submissions marked as aborted.
if submission.status == 'D':
return
- response = judge_request({'name': 'terminate-submission', 'submission-id': submission.id})
+ response = judge_request(
+ {'name': 'terminate-submission', 'submission-id': submission.id}
+ )
# This defaults to true, so that in the case the JudgeList fails to remove the submission from the queue,
# and returns a bad-request, the submission is not falsely shown as "Aborted" when it will still be judged.
if not response.get('judge-aborted', True):
- Submission.objects.filter(id=submission.id).update(status='AB', result='AB', points=0)
- event.post('sub_%s' % Submission.get_id_secret(submission.id), {'type': 'aborted'})
+ Submission.objects.filter(id=submission.id).update(
+ status='AB', result='AB', points=0
+ )
+ event.post(
+ 'sub_%s' % Submission.get_id_secret(submission.id), {'type': 'aborted'}
+ )
_post_update_submission(submission, done=True)
diff --git a/judge/lxml_tree.py b/judge/lxml_tree.py
index 014f7dc6e3..6095054d27 100644
--- a/judge/lxml_tree.py
+++ b/judge/lxml_tree.py
@@ -12,7 +12,9 @@ def __init__(self, str):
try:
self._tree = html.fromstring(str, parser=html.HTMLParser(recover=True))
except (XMLSyntaxError, ParserError) as e:
- if str and (not isinstance(e, ParserError) or e.args[0] != 'Document is empty'):
+ if str and (
+ not isinstance(e, ParserError) or e.args[0] != 'Document is empty'
+ ):
logger.exception('Failed to parse HTML string')
self._tree = html.Element('div')
diff --git a/judge/management/commands/adduser.py b/judge/management/commands/adduser.py
index c89737e4ef..f73d1cee38 100644
--- a/judge/management/commands/adduser.py
+++ b/judge/management/commands/adduser.py
@@ -12,13 +12,25 @@ def add_arguments(self, parser):
parser.add_argument('name', help='username')
parser.add_argument('email', help='email, not necessary to be resolvable')
parser.add_argument('password', help='password for the user')
- parser.add_argument('language', nargs='?', default=settings.DEFAULT_USER_LANGUAGE,
- help='default language ID for user')
+ parser.add_argument(
+ 'language',
+ nargs='?',
+ default=settings.DEFAULT_USER_LANGUAGE,
+ help='default language ID for user',
+ )
- parser.add_argument('--superuser', action='store_true', default=False,
- help='if specified, creates user with superuser privileges')
- parser.add_argument('--staff', action='store_true', default=False,
- help='if specified, creates user with staff privileges')
+ parser.add_argument(
+ '--superuser',
+ action='store_true',
+ default=False,
+ help='if specified, creates user with superuser privileges',
+ )
+ parser.add_argument(
+ '--staff',
+ action='store_true',
+ default=False,
+ help='if specified, creates user with staff privileges',
+ )
def handle(self, *args, **options):
usr = User(username=options['name'], email=options['email'], is_active=True)
diff --git a/judge/management/commands/copy_language.py b/judge/management/commands/copy_language.py
index 233ccc7e0d..026b7e2f36 100644
--- a/judge/management/commands/copy_language.py
+++ b/judge/management/commands/copy_language.py
@@ -22,6 +22,12 @@ def handle(self, *args, **options):
raise CommandError('Invalid target language: %s' % options['target'])
target.problem_set.set(source.problem_set.all())
- LanguageLimit.objects.bulk_create(LanguageLimit(problem=ll.problem, language=target, time_limit=ll.time_limit,
- memory_limit=ll.memory_limit)
- for ll in LanguageLimit.objects.filter(language=source))
+ LanguageLimit.objects.bulk_create(
+ LanguageLimit(
+ problem=ll.problem,
+ language=target,
+ time_limit=ll.time_limit,
+ memory_limit=ll.memory_limit,
+ )
+ for ll in LanguageLimit.objects.filter(language=source)
+ )
diff --git a/judge/management/commands/generate_api_token.py b/judge/management/commands/generate_api_token.py
index c65055421c..dd826f78bf 100644
--- a/judge/management/commands/generate_api_token.py
+++ b/judge/management/commands/generate_api_token.py
@@ -15,6 +15,8 @@ def add_arguments(self, parser):
def handle(self, *args, **options):
try:
- print(Profile.objects.get(user__username=options['name']).generate_api_token())
+ print(
+ Profile.objects.get(user__username=options['name']).generate_api_token()
+ )
except Profile.DoesNotExist:
raise User.DoesNotExist('User %s does not exist' % options['name'])
diff --git a/judge/management/commands/generate_sitemap.py b/judge/management/commands/generate_sitemap.py
index d71912b184..7406505f10 100644
--- a/judge/management/commands/generate_sitemap.py
+++ b/judge/management/commands/generate_sitemap.py
@@ -14,10 +14,19 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('directory', help='directory to generate the sitemap in')
- parser.add_argument('-s', '--site', type=int, help='ID of the site to generate the sitemap for')
- parser.add_argument('-p', '--protocol', default='https', help='protocol to use for links')
- parser.add_argument('-d', '--subdir', '--subdirectory', default='sitemaps',
- help='subdirectory for individual sitemap files')
+ parser.add_argument(
+ '-s', '--site', type=int, help='ID of the site to generate the sitemap for'
+ )
+ parser.add_argument(
+ '-p', '--protocol', default='https', help='protocol to use for links'
+ )
+ parser.add_argument(
+ '-d',
+ '--subdir',
+ '--subdirectory',
+ default='sitemaps',
+ help='subdirectory for individual sitemap files',
+ )
parser.add_argument('-P', '--prefix', help='URL prefix for individual sitemaps')
def handle(self, *args, **options):
@@ -27,7 +36,11 @@ def handle(self, *args, **options):
verbose = options['verbosity'] > 1
try:
- site = Site.objects.get(id=options['site']) if options['site'] else Site.objects.get_current()
+ site = (
+ Site.objects.get(id=options['site'])
+ if options['site']
+ else Site.objects.get_current()
+ )
except Site.DoesNotExist:
self.stderr.write('Pass a valid site ID for -s/--site.')
sys.exit(1)
@@ -38,7 +51,9 @@ def handle(self, *args, **options):
prefix = options['prefix'] or f'{protocol}://{site.domain}/{subdirectory}/'
if not prefix.endswith('/'):
- self.stderr.write('-P/--prefix needs to end with a / or bad things will happen.')
+ self.stderr.write(
+ '-P/--prefix needs to end with a / or bad things will happen.'
+ )
sys.exit(1)
maps = []
@@ -66,4 +81,8 @@ def handle(self, *args, **options):
self.stdout.write('Rendering sitemap index file...')
with open(directory / 'sitemap.xml', 'w', encoding='utf-8') as f:
- f.write(index_template.render({'sitemaps': [urljoin(prefix, file) for file in maps]}))
+ f.write(
+ index_template.render(
+ {'sitemaps': [urljoin(prefix, file) for file in maps]}
+ )
+ )
diff --git a/judge/management/commands/makedmojmessages.py b/judge/management/commands/makedmojmessages.py
index f162974bf3..8ad45690c3 100644
--- a/judge/management/commands/makedmojmessages.py
+++ b/judge/management/commands/makedmojmessages.py
@@ -5,26 +5,62 @@
from django.conf import settings
from django.core.management import CommandError
-from django.core.management.commands.makemessages import Command as MakeMessagesCommand, check_programs
+from django.core.management.commands.makemessages import (
+ Command as MakeMessagesCommand,
+ check_programs,
+)
from judge.models import NavigationBar, ProblemType
class Command(MakeMessagesCommand):
def add_arguments(self, parser):
- parser.add_argument('--locale', '-l', default=[], dest='locale', action='append',
- help='Creates or updates the message files for the given locale(s) (e.g. pt_BR). '
- 'Can be used multiple times.')
- parser.add_argument('--exclude', '-x', default=[], dest='exclude', action='append',
- help='Locales to exclude. Default is none. Can be used multiple times.')
- parser.add_argument('--all', '-a', action='store_true', dest='all',
- default=False, help='Updates the message files for all existing locales.')
- parser.add_argument('--no-wrap', action='store_true', dest='no_wrap',
- default=False, help="Don't break long message lines into several lines.")
- parser.add_argument('--no-obsolete', action='store_true', dest='no_obsolete',
- default=False, help='Remove obsolete message strings.')
- parser.add_argument('--keep-pot', action='store_true', dest='keep_pot',
- default=False, help='Keep .pot file after making messages. Useful when debugging.')
+ parser.add_argument(
+ '--locale',
+ '-l',
+ default=[],
+ dest='locale',
+ action='append',
+ help='Creates or updates the message files for the given locale(s) (e.g. pt_BR). '
+ 'Can be used multiple times.',
+ )
+ parser.add_argument(
+ '--exclude',
+ '-x',
+ default=[],
+ dest='exclude',
+ action='append',
+ help='Locales to exclude. Default is none. Can be used multiple times.',
+ )
+ parser.add_argument(
+ '--all',
+ '-a',
+ action='store_true',
+ dest='all',
+ default=False,
+ help='Updates the message files for all existing locales.',
+ )
+ parser.add_argument(
+ '--no-wrap',
+ action='store_true',
+ dest='no_wrap',
+ default=False,
+ help="Don't break long message lines into several lines.",
+ )
+ parser.add_argument(
+ '--no-obsolete',
+ action='store_true',
+ dest='no_obsolete',
+ default=False,
+ help='Remove obsolete message strings.',
+ )
+ parser.add_argument(
+ '--keep-pot',
+ action='store_true',
+ dest='keep_pot',
+ default=False,
+ help='Keep .pot file after making messages. Useful when debugging.',
+ )
def handle(self, *args, **options):
locale = options.get('locale')
@@ -55,8 +91,10 @@ def handle(self, *args, **options):
self.keep_pot = options.get('keep_pot')
if locale is None and not exclude and not process_all:
- raise CommandError("Type '%s help %s' for usage information." % (
- os.path.basename(sys.argv[0]), sys.argv[1]))
+ raise CommandError(
+ "Type '%s help %s' for usage information."
+ % (os.path.basename(sys.argv[0]), sys.argv[1])
+ )
self.invoked_for_django = False
self.locale_paths = []
@@ -76,7 +114,9 @@ def handle(self, *args, **options):
os.makedirs(self.default_locale_path)
# Build locale list
- locale_dirs = list(filter(os.path.isdir, glob.glob('%s/*' % self.default_locale_path)))
+ locale_dirs = list(
+ filter(os.path.isdir, glob.glob('%s/*' % self.default_locale_path))
+ )
all_locales = list(map(os.path.basename, locale_dirs))
# Account for excluded locales
@@ -108,13 +148,23 @@ def find_files(self, root):
return []
def _emit_message(self, potfile, string):
- potfile.write("""
+ potfile.write(
+ """
msgid "%s"
msgstr ""
-""" % string.replace('\\', r'\\').replace('\t', '\\t').replace('\n', '\\n').replace('"', '\\"'))
+"""
+ % string.replace('\\', r'\\')
+ .replace('\t', '\\t')
+ .replace('\n', '\\n')
+ .replace('"', '\\"')
+ )
def process_files(self, file_list):
- with io.open(os.path.join(self.default_locale_path, 'dmoj-user.pot'), 'w', encoding='utf-8') as potfile:
+ with io.open(
+ os.path.join(self.default_locale_path, 'dmoj-user.pot'),
+ 'w',
+ encoding='utf-8',
+ ) as potfile:
if self.verbosity > 1:
self.stdout.write('processing navigation bar')
for label in NavigationBar.objects.values_list('label', flat=True):
diff --git a/judge/management/commands/move_user_content.py b/judge/management/commands/move_user_content.py
index aad026732a..c1ac3279d7 100644
--- a/judge/management/commands/move_user_content.py
+++ b/judge/management/commands/move_user_content.py
@@ -23,7 +23,9 @@ def handle(self, *args, **options):
raise CommandError(f'Invalid target user: {options["target"]}')
if ContestParticipation.objects.filter(user=source).exists():
- raise CommandError(f'Cannot move user {options["source"]} because it has contest participations.')
+ raise CommandError(
+ f'Cannot move user {options["source"]} because it has contest participations.'
+ )
with transaction.atomic():
Submission.objects.filter(user=source).update(user=target)
diff --git a/judge/management/commands/render_pdf.py b/judge/management/commands/render_pdf.py
index 7f34d36f4a..c6f59cb645 100644
--- a/judge/management/commands/render_pdf.py
+++ b/judge/management/commands/render_pdf.py
@@ -12,8 +12,12 @@ class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('code', help='code of problem to render')
- parser.add_argument('-l', '--language', default=settings.LANGUAGE_CODE,
- help='language to render PDF in')
+ parser.add_argument(
+ '-l',
+ '--language',
+ default=settings.LANGUAGE_CODE,
+ help='language to render PDF in',
+ )
def handle(self, *args, **options):
try:
@@ -27,14 +31,25 @@ def handle(self, *args, **options):
except ProblemTranslation.DoesNotExist:
trans = None
- with open(problem.code + '.pdf', 'wb') as f, translation.override(options['language']):
+ with open(problem.code + '.pdf', 'wb') as f, translation.override(
+ options['language']
+ ):
problem_name = problem.name if trans is None else trans.name
- f.write(render_pdf(
- html=get_template('problem/raw.html').render({
- 'problem': problem,
- 'problem_name': problem_name,
- 'description': problem.description if trans is None else trans.description,
- 'url': '',
- }).replace('"//', '"https://').replace("'//", "'https://"),
- title=problem_name,
- ))
+ f.write(
+ render_pdf(
+ html=get_template('problem/raw.html')
+ .render(
+ {
+ 'problem': problem,
+ 'problem_name': problem_name,
+ 'description': problem.description
+ if trans is None
+ else trans.description,
+ 'url': '',
+ }
+ )
+ .replace('"//', '"https://')
+ .replace("'//", "'https://"),
+ title=problem_name,
+ )
+ )
diff --git a/judge/management/commands/runmoss.py b/judge/management/commands/runmoss.py
index 2404984f08..16f96e5a08 100644
--- a/judge/management/commands/runmoss.py
+++ b/judge/management/commands/runmoss.py
@@ -30,17 +30,25 @@ def handle(self, *args, **options):
for dmoj_lang, moss_lang in self.LANG_MAPPING:
print('%s: ' % dmoj_lang, end=' ')
subs = Submission.objects.filter(
- contest__participation__virtual__in=(ContestParticipation.LIVE, ContestParticipation.SPECTATE),
+ contest__participation__virtual__in=(
+ ContestParticipation.LIVE,
+ ContestParticipation.SPECTATE,
+ ),
contest__participation__contest__key=contest,
- result='AC', problem__id=problem.id,
+ result='AC',
+ problem__id=problem.id,
language__common_name=dmoj_lang,
).values_list('user__user__username', 'source__source')
if not subs:
print('')
continue
- moss_call = MOSS(moss_api_key, language=moss_lang, matching_file_limit=100,
- comment='%s - %s' % (contest, problem.code))
+ moss_call = MOSS(
+ moss_api_key,
+ language=moss_lang,
+ matching_file_limit=100,
+ comment='%s - %s' % (contest, problem.code),
+ )
users = set()
diff --git a/judge/middleware.py b/judge/middleware.py
index abad636af4..29b101efd6 100644
--- a/judge/middleware.py
+++ b/judge/middleware.py
@@ -27,7 +27,9 @@ def __init__(self, get_response):
def __call__(self, request):
try:
- callback, args, kwargs = resolve(request.path_info, getattr(request, 'urlconf', None))
+ callback, args, kwargs = resolve(
+ request.path_info, getattr(request, 'urlconf', None)
+ )
except Resolver404:
callback, args, kwargs = None, None, None
@@ -53,15 +55,29 @@ def __call__(self, request):
change_password_path = reverse('password_change')
change_password_done_path = reverse('password_change_done')
has_2fa = profile.is_totp_enabled or profile.is_webauthn_enabled
- if (has_2fa and not request.session.get('2fa_passed', False) and
- request.path not in (login_2fa_path, logout_path, webauthn_path) and
- not request.path.startswith(settings.STATIC_URL)):
- return HttpResponseRedirect(login_2fa_path + '?next=' + quote(request.get_full_path()))
- elif (request.session.get('password_pwned', False) and
- request.path not in (change_password_path, change_password_done_path,
- login_2fa_path, logout_path) and
- not request.path.startswith(settings.STATIC_URL)):
- return HttpResponseRedirect(change_password_path + '?next=' + quote(request.get_full_path()))
+ if (
+ has_2fa
+ and not request.session.get('2fa_passed', False)
+ and request.path not in (login_2fa_path, logout_path, webauthn_path)
+ and not request.path.startswith(settings.STATIC_URL)
+ ):
+ return HttpResponseRedirect(
+ login_2fa_path + '?next=' + quote(request.get_full_path())
+ )
+ elif (
+ request.session.get('password_pwned', False)
+ and request.path
+ not in (
+ change_password_path,
+ change_password_done_path,
+ login_2fa_path,
+ logout_path,
+ )
+ and not request.path.startswith(settings.STATIC_URL)
+ ):
+ return HttpResponseRedirect(
+ change_password_path + '?next=' + quote(request.get_full_path())
+ )
else:
request.profile = None
return self.get_response(request)
@@ -74,7 +90,10 @@ def __init__(self, get_response):
def __call__(self, request):
if request.user.is_impersonate:
if uwsgi:
- uwsgi.set_logvar('username', f'{request.impersonator.username} as {request.user.username}')
+ uwsgi.set_logvar(
+ 'username',
+ f'{request.impersonator.username} as {request.user.username}',
+ )
request.no_profile_update = True
request.profile = request.user.profile
return self.get_response(request)
@@ -114,7 +133,9 @@ def __call__(self, request):
return HttpResponse('Admin inaccessible', status=403)
try:
- id, secret = struct.unpack('>I32s', base64.urlsafe_b64decode(token.group(1)))
+ id, secret = struct.unpack(
+ '>I32s', base64.urlsafe_b64decode(token.group(1))
+ )
request.user = User.objects.get(id=id)
# User hasn't generated a token
@@ -122,7 +143,9 @@ def __call__(self, request):
raise HTTPError()
# Token comparison
- digest = hmac.new(force_bytes(settings.SECRET_KEY), msg=secret, digestmod='sha256').hexdigest()
+ digest = hmac.new(
+ force_bytes(settings.SECRET_KEY), msg=secret, digestmod='sha256'
+ ).hexdigest()
if not hmac.compare_digest(digest, request.user.profile.api_token):
raise HTTPError()
@@ -176,5 +199,7 @@ def __init__(self, get_response):
def __call__(self, request):
domain = get_current_site(request).domain
- request.misc_config = MiscConfigDict(language=request.LANGUAGE_CODE, domain=domain)
+ request.misc_config = MiscConfigDict(
+ language=request.LANGUAGE_CODE, domain=domain
+ )
return self.get_response(request)
diff --git a/judge/migrations/0001_squashed_0084_contest_formats.py b/judge/migrations/0001_squashed_0084_contest_formats.py
index 22aa65974d..55bf667922 100644
--- a/judge/migrations/0001_squashed_0084_contest_formats.py
+++ b/judge/migrations/0001_squashed_0084_contest_formats.py
@@ -13,8 +13,92 @@
class Migration(migrations.Migration):
-
- replaces = [('judge', '0001_initial'), ('judge', '0002_license'), ('judge', '0003_license_key'), ('judge', '0004_language_limit'), ('judge', '0005_nav_path_len'), ('judge', '0006_language_extension'), ('judge', '0007_test_site_perm'), ('judge', '0008_contestproblem_order'), ('judge', '0009_solution_problem'), ('judge', '0010_comment_page_index'), ('judge', '0011_organization_is_open'), ('judge', '0012_organization_perms'), ('judge', '0013_private_contests'), ('judge', '0014_multi_organization'), ('judge', '0015_remove_single_organization'), ('judge', '0016_organizationrequest'), ('judge', '0017_edit_public_problem_perm'), ('judge', '0018_django_1_9'), ('judge', '0019_og_images'), ('judge', '0020_profile_user_script'), ('judge', '0021_output_prefix_override'), ('judge', '0022_judge_last_ping'), ('judge', '0023_contest_tag'), ('judge', '0024_submission_judge'), ('judge', '0025_submission_rejudge_flag'), ('judge', '0026_change_public_visibility_perm'), ('judge', '0027_bridge_revert'), ('judge', '0028_judge_ip'), ('judge', '0029_problem_translation'), ('judge', '0030_remove_contest_profile'), ('judge', '0031_judge_versions'), ('judge', '0032_hide_problem_tags_in_contest'), ('judge', '0033_proper_pretest_support'), ('judge', '0034_submission_is_pretested'), ('judge', '0035_contest_spectate_mode'), ('judge', '0036_contest_participation_unique'), ('judge', '0037_user_count_ac_rate_field'), ('judge', '0038_profile_problem_count'), ('judge', '0039_remove_contest_is_external'), ('judge', '0040_profile_math_engine'), ('judge', '0041_virtual_contest_participation'), ('judge', '0042_remove_spectate_field'), ('judge', '0043_contest_user_count'), ('judge', '0044_organization_slots'), ('judge', '0045_organization_access_code'), ('judge', '0046_blogpost_authors'), ('judge', '0047_site_managed_data'), ('judge', '0048_site_managed_checkers'), ('judge', '0049_contest_summary'), ('judge', '0050_problem_tester_field'), ('judge', '0051_was_rejudged_field'), ('judge', '0052_switch_to_durationfield'), ('judge', '0053_opengraph_problems'), ('judge', '0054_tickets'), ('judge', '0055_add_performance_points'), ('judge', '0056_ticket_is_open'), ('judge', '0057_blue_pretests'), ('judge', '0058_problem_curator_field'), ('judge', '0059_problem_is_manually_managed'), ('judge', '0060_contest_clarifications'), ('judge', '0061_language_template'), ('judge', '0062_add_contest_submission_limit'), ('judge', '0063_new_solutions'), ('judge', '0064_unique_solution'), ('judge', '0065_blogpost_perms'), ('judge', '0066_submission_date_index'), ('judge', '0067_contest_access_code'), ('judge', '0068_hide_scoreboard'), ('judge', '0069_judge_blocking'), ('judge', '0070_organization_slug'), ('judge', '0071_organization_private_problems'), ('judge', '0072_contest_logo_override_image'), ('judge', '0073_comment_lock'), ('judge', '0074_totp'), ('judge', '0075_organization_admin_reverse'), ('judge', '0076_problem_statistics'), ('judge', '0077_remove_organization_key'), ('judge', '0078_add_user_notes'), ('judge', '0079_remove_comment_title'), ('judge', '0080_contest_banned_users'), ('judge', '0081_unlisted_users'), ('judge', '0082_remove_profile_name'), ('judge', '0083_extended_feedback'), ('judge', '0084_contest_formats')]
+ replaces = [
+ ('judge', '0001_initial'),
+ ('judge', '0002_license'),
+ ('judge', '0003_license_key'),
+ ('judge', '0004_language_limit'),
+ ('judge', '0005_nav_path_len'),
+ ('judge', '0006_language_extension'),
+ ('judge', '0007_test_site_perm'),
+ ('judge', '0008_contestproblem_order'),
+ ('judge', '0009_solution_problem'),
+ ('judge', '0010_comment_page_index'),
+ ('judge', '0011_organization_is_open'),
+ ('judge', '0012_organization_perms'),
+ ('judge', '0013_private_contests'),
+ ('judge', '0014_multi_organization'),
+ ('judge', '0015_remove_single_organization'),
+ ('judge', '0016_organizationrequest'),
+ ('judge', '0017_edit_public_problem_perm'),
+ ('judge', '0018_django_1_9'),
+ ('judge', '0019_og_images'),
+ ('judge', '0020_profile_user_script'),
+ ('judge', '0021_output_prefix_override'),
+ ('judge', '0022_judge_last_ping'),
+ ('judge', '0023_contest_tag'),
+ ('judge', '0024_submission_judge'),
+ ('judge', '0025_submission_rejudge_flag'),
+ ('judge', '0026_change_public_visibility_perm'),
+ ('judge', '0027_bridge_revert'),
+ ('judge', '0028_judge_ip'),
+ ('judge', '0029_problem_translation'),
+ ('judge', '0030_remove_contest_profile'),
+ ('judge', '0031_judge_versions'),
+ ('judge', '0032_hide_problem_tags_in_contest'),
+ ('judge', '0033_proper_pretest_support'),
+ ('judge', '0034_submission_is_pretested'),
+ ('judge', '0035_contest_spectate_mode'),
+ ('judge', '0036_contest_participation_unique'),
+ ('judge', '0037_user_count_ac_rate_field'),
+ ('judge', '0038_profile_problem_count'),
+ ('judge', '0039_remove_contest_is_external'),
+ ('judge', '0040_profile_math_engine'),
+ ('judge', '0041_virtual_contest_participation'),
+ ('judge', '0042_remove_spectate_field'),
+ ('judge', '0043_contest_user_count'),
+ ('judge', '0044_organization_slots'),
+ ('judge', '0045_organization_access_code'),
+ ('judge', '0046_blogpost_authors'),
+ ('judge', '0047_site_managed_data'),
+ ('judge', '0048_site_managed_checkers'),
+ ('judge', '0049_contest_summary'),
+ ('judge', '0050_problem_tester_field'),
+ ('judge', '0051_was_rejudged_field'),
+ ('judge', '0052_switch_to_durationfield'),
+ ('judge', '0053_opengraph_problems'),
+ ('judge', '0054_tickets'),
+ ('judge', '0055_add_performance_points'),
+ ('judge', '0056_ticket_is_open'),
+ ('judge', '0057_blue_pretests'),
+ ('judge', '0058_problem_curator_field'),
+ ('judge', '0059_problem_is_manually_managed'),
+ ('judge', '0060_contest_clarifications'),
+ ('judge', '0061_language_template'),
+ ('judge', '0062_add_contest_submission_limit'),
+ ('judge', '0063_new_solutions'),
+ ('judge', '0064_unique_solution'),
+ ('judge', '0065_blogpost_perms'),
+ ('judge', '0066_submission_date_index'),
+ ('judge', '0067_contest_access_code'),
+ ('judge', '0068_hide_scoreboard'),
+ ('judge', '0069_judge_blocking'),
+ ('judge', '0070_organization_slug'),
+ ('judge', '0071_organization_private_problems'),
+ ('judge', '0072_contest_logo_override_image'),
+ ('judge', '0073_comment_lock'),
+ ('judge', '0074_totp'),
+ ('judge', '0075_organization_admin_reverse'),
+ ('judge', '0076_problem_statistics'),
+ ('judge', '0077_remove_organization_key'),
+ ('judge', '0078_add_user_notes'),
+ ('judge', '0079_remove_comment_title'),
+ ('judge', '0080_contest_banned_users'),
+ ('judge', '0081_unlisted_users'),
+ ('judge', '0082_remove_profile_name'),
+ ('judge', '0083_extended_feedback'),
+ ('judge', '0084_contest_formats'),
+ ]
initial = True
@@ -27,15 +111,36 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='BlogPost',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('title', models.CharField(max_length=100, verbose_name='post title')),
('slug', models.SlugField(verbose_name='slug')),
- ('visible', models.BooleanField(default=False, verbose_name='public visibility')),
+ (
+ 'visible',
+ models.BooleanField(
+ default=False, verbose_name='public visibility'
+ ),
+ ),
('sticky', models.BooleanField(default=False, verbose_name='sticky')),
('publish_on', models.DateTimeField(verbose_name='publish after')),
('content', models.TextField(verbose_name='post content')),
('summary', models.TextField(blank=True, verbose_name='post summary')),
- ('og_image', models.CharField(blank=True, default='', max_length=150, verbose_name='openGraph image')),
+ (
+ 'og_image',
+ models.CharField(
+ blank=True,
+ default='',
+ max_length=150,
+ verbose_name='openGraph image',
+ ),
+ ),
],
options={
'verbose_name_plural': 'blog posts',
@@ -46,12 +151,42 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Comment',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('time', models.DateTimeField(auto_now_add=True, verbose_name='posted time')),
- ('page', models.CharField(db_index=True, max_length=30, validators=[django.core.validators.RegexValidator('^[pcs]:[a-z0-9]+$|^b:\\d+$', 'Page code must be ^[pcs]:[a-z0-9]+$|^b:\\d+$')], verbose_name='associated page')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'time',
+ models.DateTimeField(auto_now_add=True, verbose_name='posted time'),
+ ),
+ (
+ 'page',
+ models.CharField(
+ db_index=True,
+ max_length=30,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[pcs]:[a-z0-9]+$|^b:\\d+$',
+ 'Page code must be ^[pcs]:[a-z0-9]+$|^b:\\d+$',
+ )
+ ],
+ verbose_name='associated page',
+ ),
+ ),
('score', models.IntegerField(default=0, verbose_name='votes')),
- ('body', models.TextField(max_length=8192, verbose_name='body of comment')),
- ('hidden', models.BooleanField(default=0, verbose_name='hide the comment')),
+ (
+ 'body',
+ models.TextField(max_length=8192, verbose_name='body of comment'),
+ ),
+ (
+ 'hidden',
+ models.BooleanField(default=0, verbose_name='hide the comment'),
+ ),
('lft', models.PositiveIntegerField(db_index=True, editable=False)),
('rght', models.PositiveIntegerField(db_index=True, editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
@@ -65,8 +200,29 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='CommentLock',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('page', models.CharField(db_index=True, max_length=30, validators=[django.core.validators.RegexValidator('^[pcs]:[a-z0-9]+$|^b:\\d+$', 'Page code must be ^[pcs]:[a-z0-9]+$|^b:\\d+$')], verbose_name='associated page')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'page',
+ models.CharField(
+ db_index=True,
+ max_length=30,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[pcs]:[a-z0-9]+$|^b:\\d+$',
+ 'Page code must be ^[pcs]:[a-z0-9]+$|^b:\\d+$',
+ )
+ ],
+ verbose_name='associated page',
+ ),
+ ),
],
options={
'permissions': (('override_comment_lock', 'Override comment lock'),),
@@ -75,9 +231,24 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='CommentVote',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('score', models.IntegerField()),
- ('comment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='votes', to='judge.Comment')),
+ (
+ 'comment',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='votes',
+ to='judge.Comment',
+ ),
+ ),
],
options={
'verbose_name_plural': 'comment votes',
@@ -87,45 +258,244 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Contest',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('key', models.CharField(max_length=20, unique=True, validators=[django.core.validators.RegexValidator('^[a-z0-9]+$', 'Contest id must be ^[a-z0-9]+$')], verbose_name='contest id')),
- ('name', models.CharField(db_index=True, max_length=100, verbose_name='contest name')),
- ('description', models.TextField(blank=True, verbose_name='description')),
- ('start_time', models.DateTimeField(db_index=True, verbose_name='start time')),
- ('end_time', models.DateTimeField(db_index=True, verbose_name='end time')),
- ('time_limit', models.DurationField(blank=True, null=True, verbose_name='time limit')),
- ('is_public', models.BooleanField(default=False, help_text='Should be set even for organization-private contests, where it determines whether the contest is visible to members of the specified organizations.', verbose_name='publicly visible')),
- ('is_rated', models.BooleanField(default=False, help_text='Whether this contest can be rated.', verbose_name='contest rated')),
- ('hide_scoreboard', models.BooleanField(default=False, help_text='Whether the scoreboard should remain hidden for the duration of the contest.', verbose_name='hide scoreboard')),
- ('use_clarifications', models.BooleanField(default=True, help_text='Use clarification system instead of comments.', verbose_name='no comments')),
- ('rate_all', models.BooleanField(default=False, help_text='Rate all users who joined.', verbose_name='rate all')),
- ('is_private', models.BooleanField(default=False, verbose_name='private to organizations')),
- ('hide_problem_tags', models.BooleanField(default=False, help_text='Whether problem tags should be hidden by default.', verbose_name='hide problem tags')),
- ('run_pretests_only', models.BooleanField(default=False, help_text='Whether judges should grade pretests only, versus all testcases. Commonly set during a contest, then unset prior to rejudging user submissions when the contest ends.', verbose_name='run pretests only')),
- ('og_image', models.CharField(blank=True, default='', max_length=150, verbose_name='OpenGraph image')),
- ('logo_override_image', models.CharField(blank=True, default='', help_text='This image will replace the default site logo for users inside the contest.', max_length=150, verbose_name='Logo override image')),
- ('user_count', models.IntegerField(default=0, verbose_name='the amount of live participants')),
- ('summary', models.TextField(blank=True, help_text='Plain-text, shown in meta description tag, e.g. for social media.', verbose_name='contest summary')),
- ('access_code', models.CharField(blank=True, default='', help_text='An optional code to prompt contestants before they are allowed to join the contest. Leave it blank to disable.', max_length=255, verbose_name='access code')),
- ('format_name', models.CharField(choices=[('default', 'Default')], default='default', help_text='The contest format module to use.', max_length=32, verbose_name='contest format')),
- ('format_config', jsonfield.fields.JSONField(blank=True, help_text='A JSON object to serve as the configuration for the chosen contest format module. Leave empty to use None. Exact format depends on the contest format selected.', null=True, verbose_name='contest format configuration')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'key',
+ models.CharField(
+ max_length=20,
+ unique=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[a-z0-9]+$', 'Contest id must be ^[a-z0-9]+$'
+ )
+ ],
+ verbose_name='contest id',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(
+ db_index=True, max_length=100, verbose_name='contest name'
+ ),
+ ),
+ (
+ 'description',
+ models.TextField(blank=True, verbose_name='description'),
+ ),
+ (
+ 'start_time',
+ models.DateTimeField(db_index=True, verbose_name='start time'),
+ ),
+ (
+ 'end_time',
+ models.DateTimeField(db_index=True, verbose_name='end time'),
+ ),
+ (
+ 'time_limit',
+ models.DurationField(
+ blank=True, null=True, verbose_name='time limit'
+ ),
+ ),
+ (
+ 'is_public',
+ models.BooleanField(
+ default=False,
+ help_text='Should be set even for organization-private contests, where it determines whether the contest is visible to members of the specified organizations.',
+ verbose_name='publicly visible',
+ ),
+ ),
+ (
+ 'is_rated',
+ models.BooleanField(
+ default=False,
+ help_text='Whether this contest can be rated.',
+ verbose_name='contest rated',
+ ),
+ ),
+ (
+ 'hide_scoreboard',
+ models.BooleanField(
+ default=False,
+ help_text='Whether the scoreboard should remain hidden for the duration of the contest.',
+ verbose_name='hide scoreboard',
+ ),
+ ),
+ (
+ 'use_clarifications',
+ models.BooleanField(
+ default=True,
+ help_text='Use clarification system instead of comments.',
+ verbose_name='no comments',
+ ),
+ ),
+ (
+ 'rate_all',
+ models.BooleanField(
+ default=False,
+ help_text='Rate all users who joined.',
+ verbose_name='rate all',
+ ),
+ ),
+ (
+ 'is_private',
+ models.BooleanField(
+ default=False, verbose_name='private to organizations'
+ ),
+ ),
+ (
+ 'hide_problem_tags',
+ models.BooleanField(
+ default=False,
+ help_text='Whether problem tags should be hidden by default.',
+ verbose_name='hide problem tags',
+ ),
+ ),
+ (
+ 'run_pretests_only',
+ models.BooleanField(
+ default=False,
+ help_text='Whether judges should grade pretests only, versus all testcases. Commonly set during a contest, then unset prior to rejudging user submissions when the contest ends.',
+ verbose_name='run pretests only',
+ ),
+ ),
+ (
+ 'og_image',
+ models.CharField(
+ blank=True,
+ default='',
+ max_length=150,
+ verbose_name='OpenGraph image',
+ ),
+ ),
+ (
+ 'logo_override_image',
+ models.CharField(
+ blank=True,
+ default='',
+ help_text='This image will replace the default site logo for users inside the contest.',
+ max_length=150,
+ verbose_name='Logo override image',
+ ),
+ ),
+ (
+ 'user_count',
+ models.IntegerField(
+ default=0, verbose_name='the amount of live participants'
+ ),
+ ),
+ (
+ 'summary',
+ models.TextField(
+ blank=True,
+ help_text='Plain-text, shown in meta description tag, e.g. for social media.',
+ verbose_name='contest summary',
+ ),
+ ),
+ (
+ 'access_code',
+ models.CharField(
+ blank=True,
+ default='',
+ help_text='An optional code to prompt contestants before they are allowed to join the contest. Leave it blank to disable.',
+ max_length=255,
+ verbose_name='access code',
+ ),
+ ),
+ (
+ 'format_name',
+ models.CharField(
+ choices=[('default', 'Default')],
+ default='default',
+ help_text='The contest format module to use.',
+ max_length=32,
+ verbose_name='contest format',
+ ),
+ ),
+ (
+ 'format_config',
+ jsonfield.fields.JSONField(
+ blank=True,
+ help_text='A JSON object to serve as the configuration for the chosen contest format module. Leave empty to use None. Exact format depends on the contest format selected.',
+ null=True,
+ verbose_name='contest format configuration',
+ ),
+ ),
],
options={
'verbose_name_plural': 'contests',
- 'permissions': (('see_private_contest', 'See private contests'), ('edit_own_contest', 'Edit own contests'), ('edit_all_contest', 'Edit all contests'), ('contest_rating', 'Rate contests'), ('contest_access_code', 'Contest access codes')),
+ 'permissions': (
+ ('see_private_contest', 'See private contests'),
+ ('edit_own_contest', 'Edit own contests'),
+ ('edit_all_contest', 'Edit all contests'),
+ ('contest_rating', 'Rate contests'),
+ ('contest_access_code', 'Contest access codes'),
+ ),
'verbose_name': 'contest',
},
),
migrations.CreateModel(
name='ContestParticipation',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('real_start', models.DateTimeField(db_column='start', default=django.utils.timezone.now, verbose_name='start time')),
- ('score', models.IntegerField(db_index=True, default=0, verbose_name='score')),
- ('cumtime', models.PositiveIntegerField(default=0, verbose_name='cumulative time')),
- ('virtual', models.IntegerField(default=0, help_text='0 means non-virtual, otherwise the n-th virtual participation', verbose_name='virtual participation id')),
- ('format_data', jsonfield.fields.JSONField(blank=True, null=True, verbose_name='contest format specific data')),
- ('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='users', to='judge.Contest', verbose_name='associated contest')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'real_start',
+ models.DateTimeField(
+ db_column='start',
+ default=django.utils.timezone.now,
+ verbose_name='start time',
+ ),
+ ),
+ (
+ 'score',
+ models.IntegerField(db_index=True, default=0, verbose_name='score'),
+ ),
+ (
+ 'cumtime',
+ models.PositiveIntegerField(
+ default=0, verbose_name='cumulative time'
+ ),
+ ),
+ (
+ 'virtual',
+ models.IntegerField(
+ default=0,
+ help_text='0 means non-virtual, otherwise the n-th virtual participation',
+ verbose_name='virtual participation id',
+ ),
+ ),
+ (
+ 'format_data',
+ jsonfield.fields.JSONField(
+ blank=True,
+ null=True,
+ verbose_name='contest format specific data',
+ ),
+ ),
+ (
+ 'contest',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='users',
+ to='judge.Contest',
+ verbose_name='associated contest',
+ ),
+ ),
],
options={
'verbose_name_plural': 'contest participations',
@@ -135,14 +505,54 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ContestProblem',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('points', models.IntegerField(verbose_name='points')),
('partial', models.BooleanField(default=True, verbose_name='partial')),
- ('is_pretested', models.BooleanField(default=False, verbose_name='is pretested')),
- ('order', models.PositiveIntegerField(db_index=True, verbose_name='order')),
- ('output_prefix_override', models.IntegerField(blank=True, null=True, verbose_name='output prefix length override')),
- ('max_submissions', models.IntegerField(default=0, help_text='Maximum number of submissions for this problem, or 0 for no limit.', validators=[django.core.validators.MinValueValidator(0, "Why include a problem you can't submit to?")])),
- ('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contest_problems', to='judge.Contest', verbose_name='contest')),
+ (
+ 'is_pretested',
+ models.BooleanField(default=False, verbose_name='is pretested'),
+ ),
+ (
+ 'order',
+ models.PositiveIntegerField(db_index=True, verbose_name='order'),
+ ),
+ (
+ 'output_prefix_override',
+ models.IntegerField(
+ blank=True,
+ null=True,
+ verbose_name='output prefix length override',
+ ),
+ ),
+ (
+ 'max_submissions',
+ models.IntegerField(
+ default=0,
+ help_text='Maximum number of submissions for this problem, or 0 for no limit.',
+ validators=[
+ django.core.validators.MinValueValidator(
+ 0, "Why include a problem you can't submit to?"
+ )
+ ],
+ ),
+ ),
+ (
+ 'contest',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='contest_problems',
+ to='judge.Contest',
+ verbose_name='contest',
+ ),
+ ),
],
options={
'verbose_name_plural': 'contest problems',
@@ -152,11 +562,44 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ContestSubmission',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('points', models.FloatField(default=0.0, verbose_name='points')),
- ('is_pretest', models.BooleanField(default=False, help_text='Whether this submission was ran only on pretests.', verbose_name='is pretested')),
- ('participation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', related_query_name='submission', to='judge.ContestParticipation', verbose_name='participation')),
- ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', related_query_name='submission', to='judge.ContestProblem', verbose_name='problem')),
+ (
+ 'is_pretest',
+ models.BooleanField(
+ default=False,
+ help_text='Whether this submission was ran only on pretests.',
+ verbose_name='is pretested',
+ ),
+ ),
+ (
+ 'participation',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='submissions',
+ related_query_name='submission',
+ to='judge.ContestParticipation',
+ verbose_name='participation',
+ ),
+ ),
+ (
+ 'problem',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='submissions',
+ related_query_name='submission',
+ to='judge.ContestProblem',
+ verbose_name='problem',
+ ),
+ ),
],
options={
'verbose_name_plural': 'contest submissions',
@@ -166,10 +609,45 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ContestTag',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=20, unique=True, validators=[django.core.validators.RegexValidator('^[a-z-]+$', message='Lowercase letters and hyphens only.')], verbose_name='tag name')),
- ('color', models.CharField(max_length=7, validators=[django.core.validators.RegexValidator('^#(?:[A-Fa-f0-9]{3}){1,2}$', 'Invalid colour.')], verbose_name='tag colour')),
- ('description', models.TextField(blank=True, verbose_name='tag description')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(
+ max_length=20,
+ unique=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[a-z-]+$',
+ message='Lowercase letters and hyphens only.',
+ )
+ ],
+ verbose_name='tag name',
+ ),
+ ),
+ (
+ 'color',
+ models.CharField(
+ max_length=7,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^#(?:[A-Fa-f0-9]{3}){1,2}$', 'Invalid colour.'
+ )
+ ],
+ verbose_name='tag colour',
+ ),
+ ),
+ (
+ 'description',
+ models.TextField(blank=True, verbose_name='tag description'),
+ ),
],
options={
'verbose_name_plural': 'contest tags',
@@ -179,17 +657,74 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Judge',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(help_text='Server name, hostname-style', max_length=50, unique=True)),
- ('created', models.DateTimeField(auto_now_add=True, verbose_name='time of creation')),
- ('auth_key', models.CharField(help_text='A key to authenticated this judge', max_length=100, verbose_name='authentication key')),
- ('is_blocked', models.BooleanField(default=False, help_text='Whether this judge should be blocked from connecting, even if its key is correct.', verbose_name='block judge')),
- ('online', models.BooleanField(default=False, verbose_name='judge online status')),
- ('start_time', models.DateTimeField(null=True, verbose_name='judge start time')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(
+ help_text='Server name, hostname-style',
+ max_length=50,
+ unique=True,
+ ),
+ ),
+ (
+ 'created',
+ models.DateTimeField(
+ auto_now_add=True, verbose_name='time of creation'
+ ),
+ ),
+ (
+ 'auth_key',
+ models.CharField(
+ help_text='A key to authenticated this judge',
+ max_length=100,
+ verbose_name='authentication key',
+ ),
+ ),
+ (
+ 'is_blocked',
+ models.BooleanField(
+ default=False,
+ help_text='Whether this judge should be blocked from connecting, even if its key is correct.',
+ verbose_name='block judge',
+ ),
+ ),
+ (
+ 'online',
+ models.BooleanField(
+ default=False, verbose_name='judge online status'
+ ),
+ ),
+ (
+ 'start_time',
+ models.DateTimeField(null=True, verbose_name='judge start time'),
+ ),
('ping', models.FloatField(null=True, verbose_name='response time')),
- ('load', models.FloatField(help_text='Load for the last minute, divided by processors to be fair.', null=True, verbose_name='system load')),
- ('description', models.TextField(blank=True, verbose_name='description')),
- ('last_ip', models.GenericIPAddressField(blank=True, null=True, verbose_name='Last connected IP')),
+ (
+ 'load',
+ models.FloatField(
+ help_text='Load for the last minute, divided by processors to be fair.',
+ null=True,
+ verbose_name='system load',
+ ),
+ ),
+ (
+ 'description',
+ models.TextField(blank=True, verbose_name='description'),
+ ),
+ (
+ 'last_ip',
+ models.GenericIPAddressField(
+ blank=True, null=True, verbose_name='Last connected IP'
+ ),
+ ),
],
options={
'ordering': ['name'],
@@ -200,17 +735,99 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Language',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('key', models.CharField(help_text='The identifier for this language; the same as its executor id for judges.', max_length=6, unique=True, verbose_name='short identifier')),
- ('name', models.CharField(help_text='Longer name for the language, e.g. "Python 2" or "C++11".', max_length=20, verbose_name='long name')),
- ('short_name', models.CharField(blank=True, help_text='More readable, but short, name to display publicly; e.g. "PY2" or "C++11". If left blank, it will default to the short identifier.', max_length=10, null=True, verbose_name='short name')),
- ('common_name', models.CharField(help_text='Common name for the language. For example, the common name for C++03, C++11, and C++14 would be "C++"', max_length=10, verbose_name='common name')),
- ('ace', models.CharField(help_text='Language ID for Ace.js editor highlighting, appended to "mode-" to determine the Ace JavaScript file to use, e.g., "python".', max_length=20, verbose_name='ace mode name')),
- ('pygments', models.CharField(help_text='Language ID for Pygments highlighting in source windows.', max_length=20, verbose_name='pygments name')),
- ('template', models.TextField(blank=True, help_text='Code template to display in submission editor.', verbose_name='code template')),
- ('info', models.CharField(blank=True, help_text="Do not set this unless you know what you're doing! It will override the usually more specific, judge-provided runtime info!", max_length=50, verbose_name='runtime info override')),
- ('description', models.TextField(blank=True, help_text='Use field this to inform users of quirks with your environment, additional restrictions, etc.', verbose_name='language description')),
- ('extension', models.CharField(help_text='The extension of source files, e.g., "py" or "cpp".', max_length=10, verbose_name='extension')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'key',
+ models.CharField(
+ help_text='The identifier for this language; the same as its executor id for judges.',
+ max_length=6,
+ unique=True,
+ verbose_name='short identifier',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(
+ help_text='Longer name for the language, e.g. "Python 2" or "C++11".',
+ max_length=20,
+ verbose_name='long name',
+ ),
+ ),
+ (
+ 'short_name',
+ models.CharField(
+ blank=True,
+ help_text='More readable, but short, name to display publicly; e.g. "PY2" or "C++11". If left blank, it will default to the short identifier.',
+ max_length=10,
+ null=True,
+ verbose_name='short name',
+ ),
+ ),
+ (
+ 'common_name',
+ models.CharField(
+ help_text='Common name for the language. For example, the common name for C++03, C++11, and C++14 would be "C++"',
+ max_length=10,
+ verbose_name='common name',
+ ),
+ ),
+ (
+ 'ace',
+ models.CharField(
+ help_text='Language ID for Ace.js editor highlighting, appended to "mode-" to determine the Ace JavaScript file to use, e.g., "python".',
+ max_length=20,
+ verbose_name='ace mode name',
+ ),
+ ),
+ (
+ 'pygments',
+ models.CharField(
+ help_text='Language ID for Pygments highlighting in source windows.',
+ max_length=20,
+ verbose_name='pygments name',
+ ),
+ ),
+ (
+ 'template',
+ models.TextField(
+ blank=True,
+ help_text='Code template to display in submission editor.',
+ verbose_name='code template',
+ ),
+ ),
+ (
+ 'info',
+ models.CharField(
+ blank=True,
+ help_text="Do not set this unless you know what you're doing! It will override the usually more specific, judge-provided runtime info!",
+ max_length=50,
+ verbose_name='runtime info override',
+ ),
+ ),
+ (
+ 'description',
+ models.TextField(
+ blank=True,
+ help_text='Use field this to inform users of quirks with your environment, additional restrictions, etc.',
+ verbose_name='language description',
+ ),
+ ),
+ (
+ 'extension',
+ models.CharField(
+ help_text='The extension of source files, e.g., "py" or "cpp".',
+ max_length=10,
+ verbose_name='extension',
+ ),
+ ),
],
options={
'ordering': ['key'],
@@ -221,10 +838,25 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='LanguageLimit',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('time_limit', models.FloatField(verbose_name='time limit')),
('memory_limit', models.IntegerField(verbose_name='memory limit')),
- ('language', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Language', verbose_name='language')),
+ (
+ 'language',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.Language',
+ verbose_name='language',
+ ),
+ ),
],
options={
'verbose_name_plural': 'language-specific resource limits',
@@ -234,12 +866,48 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='License',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('key', models.CharField(max_length=20, unique=True, validators=[django.core.validators.RegexValidator('^[-\\w.]+$', 'License key must be ^[-\\w.]+$')], verbose_name='key')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'key',
+ models.CharField(
+ max_length=20,
+ unique=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[-\\w.]+$', 'License key must be ^[-\\w.]+$'
+ )
+ ],
+ verbose_name='key',
+ ),
+ ),
('link', models.CharField(max_length=256, verbose_name='link')),
('name', models.CharField(max_length=256, verbose_name='full name')),
- ('display', models.CharField(blank=True, help_text='Displayed on pages under this license', max_length=256, verbose_name='short name')),
- ('icon', models.CharField(blank=True, help_text='URL to the icon', max_length=256, verbose_name='icon')),
+ (
+ 'display',
+ models.CharField(
+ blank=True,
+ help_text='Displayed on pages under this license',
+ max_length=256,
+ verbose_name='short name',
+ ),
+ ),
+ (
+ 'icon',
+ models.CharField(
+ blank=True,
+ help_text='URL to the icon',
+ max_length=256,
+ verbose_name='icon',
+ ),
+ ),
('text', models.TextField(verbose_name='license text')),
],
options={
@@ -250,7 +918,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='MiscConfig',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('key', models.CharField(db_index=True, max_length=30)),
('value', models.TextField(blank=True)),
],
@@ -262,17 +938,49 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='NavigationBar',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('order', models.PositiveIntegerField(db_index=True, verbose_name='order')),
- ('key', models.CharField(max_length=10, unique=True, verbose_name='identifier')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'order',
+ models.PositiveIntegerField(db_index=True, verbose_name='order'),
+ ),
+ (
+ 'key',
+ models.CharField(
+ max_length=10, unique=True, verbose_name='identifier'
+ ),
+ ),
('label', models.CharField(max_length=20, verbose_name='label')),
('path', models.CharField(max_length=255, verbose_name='link path')),
- ('regex', models.TextField(validators=[judge.models.interface.validate_regex], verbose_name='highlight regex')),
+ (
+ 'regex',
+ models.TextField(
+ validators=[judge.models.interface.validate_regex],
+ verbose_name='highlight regex',
+ ),
+ ),
('lft', models.PositiveIntegerField(db_index=True, editable=False)),
('rght', models.PositiveIntegerField(db_index=True, editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(db_index=True, editable=False)),
- ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='judge.NavigationBar', verbose_name='parent item')),
+ (
+ 'parent',
+ mptt.fields.TreeForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='children',
+ to='judge.NavigationBar',
+ verbose_name='parent item',
+ ),
+ ),
],
options={
'verbose_name_plural': 'navigation bar',
@@ -282,31 +990,120 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Organization',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=128, verbose_name='organization title')),
- ('slug', models.SlugField(help_text='Organization name shown in URL', max_length=128, verbose_name='organization slug')),
- ('short_name', models.CharField(help_text='Displayed beside user name during contests', max_length=20, verbose_name='short name')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(max_length=128, verbose_name='organization title'),
+ ),
+ (
+ 'slug',
+ models.SlugField(
+ help_text='Organization name shown in URL',
+ max_length=128,
+ verbose_name='organization slug',
+ ),
+ ),
+ (
+ 'short_name',
+ models.CharField(
+ help_text='Displayed beside user name during contests',
+ max_length=20,
+ verbose_name='short name',
+ ),
+ ),
('about', models.TextField(verbose_name='organization description')),
- ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='creation date')),
- ('is_open', models.BooleanField(default=True, help_text='Allow joining organization', verbose_name='is open organization?')),
- ('slots', models.IntegerField(blank=True, help_text='Maximum amount of users in this organization, only applicable to private organizations', null=True, verbose_name='maximum size')),
- ('access_code', models.CharField(blank=True, help_text='Student access code', max_length=7, null=True, verbose_name='access code')),
+ (
+ 'creation_date',
+ models.DateTimeField(
+ auto_now_add=True, verbose_name='creation date'
+ ),
+ ),
+ (
+ 'is_open',
+ models.BooleanField(
+ default=True,
+ help_text='Allow joining organization',
+ verbose_name='is open organization?',
+ ),
+ ),
+ (
+ 'slots',
+ models.IntegerField(
+ blank=True,
+ help_text='Maximum amount of users in this organization, only applicable to private organizations',
+ null=True,
+ verbose_name='maximum size',
+ ),
+ ),
+ (
+ 'access_code',
+ models.CharField(
+ blank=True,
+ help_text='Student access code',
+ max_length=7,
+ null=True,
+ verbose_name='access code',
+ ),
+ ),
],
options={
'ordering': ['name'],
'verbose_name_plural': 'organizations',
- 'permissions': (('organization_admin', 'Administer organizations'), ('edit_all_organization', 'Edit all organizations')),
+ 'permissions': (
+ ('organization_admin', 'Administer organizations'),
+ ('edit_all_organization', 'Edit all organizations'),
+ ),
'verbose_name': 'organization',
},
),
migrations.CreateModel(
name='OrganizationRequest',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('time', models.DateTimeField(auto_now_add=True, verbose_name='request time')),
- ('state', models.CharField(choices=[('P', 'Pending'), ('A', 'Approved'), ('R', 'Rejected')], max_length=1, verbose_name='state')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'time',
+ models.DateTimeField(
+ auto_now_add=True, verbose_name='request time'
+ ),
+ ),
+ (
+ 'state',
+ models.CharField(
+ choices=[
+ ('P', 'Pending'),
+ ('A', 'Approved'),
+ ('R', 'Rejected'),
+ ],
+ max_length=1,
+ verbose_name='state',
+ ),
+ ),
('reason', models.TextField(verbose_name='reason')),
- ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requests', to='judge.Organization', verbose_name='organization')),
+ (
+ 'organization',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='requests',
+ to='judge.Organization',
+ verbose_name='organization',
+ ),
+ ),
],
options={
'verbose_name_plural': 'organization join requests',
@@ -316,77 +1113,317 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='PrivateMessage',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('title', models.CharField(max_length=50, verbose_name='message title')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'title',
+ models.CharField(max_length=50, verbose_name='message title'),
+ ),
('content', models.TextField(verbose_name='message body')),
- ('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='message timestamp')),
+ (
+ 'timestamp',
+ models.DateTimeField(
+ auto_now_add=True, verbose_name='message timestamp'
+ ),
+ ),
('read', models.BooleanField(default=False, verbose_name='read')),
],
),
migrations.CreateModel(
name='PrivateMessageThread',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('messages', models.ManyToManyField(to='judge.PrivateMessage', verbose_name='messages in the thread')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'messages',
+ models.ManyToManyField(
+ to='judge.PrivateMessage', verbose_name='messages in the thread'
+ ),
+ ),
],
),
migrations.CreateModel(
name='Problem',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('code', models.CharField(max_length=20, unique=True, validators=[django.core.validators.RegexValidator('^[a-z0-9]+$', 'Problem code must be ^[a-z0-9]+$')], verbose_name='problem code')),
- ('name', models.CharField(db_index=True, max_length=100, verbose_name='problem name')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'code',
+ models.CharField(
+ max_length=20,
+ unique=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[a-z0-9]+$', 'Problem code must be ^[a-z0-9]+$'
+ )
+ ],
+ verbose_name='problem code',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(
+ db_index=True, max_length=100, verbose_name='problem name'
+ ),
+ ),
('description', models.TextField(verbose_name='problem body')),
- ('time_limit', models.FloatField(help_text='The time limit for this problem, in seconds. Fractional seconds (e.g. 1.5) are supported.', verbose_name='time limit')),
- ('memory_limit', models.IntegerField(help_text='The memory limit for this problem, in kilobytes (e.g. 64mb = 65536 kilobytes).', verbose_name='memory limit')),
+ (
+ 'time_limit',
+ models.FloatField(
+ help_text='The time limit for this problem, in seconds. Fractional seconds (e.g. 1.5) are supported.',
+ verbose_name='time limit',
+ ),
+ ),
+ (
+ 'memory_limit',
+ models.IntegerField(
+ help_text='The memory limit for this problem, in kilobytes (e.g. 64mb = 65536 kilobytes).',
+ verbose_name='memory limit',
+ ),
+ ),
('short_circuit', models.BooleanField(default=False)),
('points', models.FloatField(verbose_name='points')),
- ('partial', models.BooleanField(default=False, verbose_name='allows partial points')),
- ('is_public', models.BooleanField(db_index=True, default=False, verbose_name='publicly visible')),
- ('is_manually_managed', models.BooleanField(db_index=True, default=False, help_text='Whether judges should be allowed to manage data or not', verbose_name='manually managed')),
- ('date', models.DateTimeField(blank=True, db_index=True, help_text="Doesn't have magic ability to auto-publish due to backward compatibility", null=True, verbose_name='date of publishing')),
- ('og_image', models.CharField(blank=True, max_length=150, verbose_name='OpenGraph image')),
- ('summary', models.TextField(blank=True, help_text='Plain-text, shown in meta description tag, e.g. for social media.', verbose_name='problem summary')),
- ('user_count', models.IntegerField(default=0, help_text='The number of users who solved the problem.', verbose_name='number of users')),
+ (
+ 'partial',
+ models.BooleanField(
+ default=False, verbose_name='allows partial points'
+ ),
+ ),
+ (
+ 'is_public',
+ models.BooleanField(
+ db_index=True, default=False, verbose_name='publicly visible'
+ ),
+ ),
+ (
+ 'is_manually_managed',
+ models.BooleanField(
+ db_index=True,
+ default=False,
+ help_text='Whether judges should be allowed to manage data or not',
+ verbose_name='manually managed',
+ ),
+ ),
+ (
+ 'date',
+ models.DateTimeField(
+ blank=True,
+ db_index=True,
+ help_text="Doesn't have magic ability to auto-publish due to backward compatibility",
+ null=True,
+ verbose_name='date of publishing',
+ ),
+ ),
+ (
+ 'og_image',
+ models.CharField(
+ blank=True, max_length=150, verbose_name='OpenGraph image'
+ ),
+ ),
+ (
+ 'summary',
+ models.TextField(
+ blank=True,
+ help_text='Plain-text, shown in meta description tag, e.g. for social media.',
+ verbose_name='problem summary',
+ ),
+ ),
+ (
+ 'user_count',
+ models.IntegerField(
+ default=0,
+ help_text='The number of users who solved the problem.',
+ verbose_name='number of users',
+ ),
+ ),
('ac_rate', models.FloatField(default=0, verbose_name='solve rate')),
- ('is_organization_private', models.BooleanField(default=False, verbose_name='private to organizations')),
- ('allowed_languages', models.ManyToManyField(to='judge.Language', verbose_name='allowed languages')),
+ (
+ 'is_organization_private',
+ models.BooleanField(
+ default=False, verbose_name='private to organizations'
+ ),
+ ),
+ (
+ 'allowed_languages',
+ models.ManyToManyField(
+ to='judge.Language', verbose_name='allowed languages'
+ ),
+ ),
],
options={
'verbose_name_plural': 'problems',
- 'permissions': (('see_private_problem', 'See hidden problems'), ('edit_own_problem', 'Edit own problems'), ('edit_all_problem', 'Edit all problems'), ('edit_public_problem', 'Edit all public problems'), ('clone_problem', 'Clone problem'), ('change_public_visibility', 'Change is_public field'), ('change_manually_managed', 'Change is_manually_managed field'), ('see_organization_problem', 'See organization-private problems')),
+ 'permissions': (
+ ('see_private_problem', 'See hidden problems'),
+ ('edit_own_problem', 'Edit own problems'),
+ ('edit_all_problem', 'Edit all problems'),
+ ('edit_public_problem', 'Edit all public problems'),
+ ('clone_problem', 'Clone problem'),
+ ('change_public_visibility', 'Change is_public field'),
+ ('change_manually_managed', 'Change is_manually_managed field'),
+ ('see_organization_problem', 'See organization-private problems'),
+ ),
'verbose_name': 'problem',
},
),
migrations.CreateModel(
name='ProblemClarification',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('description', models.TextField(verbose_name='clarification body')),
- ('date', models.DateTimeField(auto_now_add=True, verbose_name='clarification timestamp')),
- ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Problem', verbose_name='clarified problem')),
+ (
+ 'date',
+ models.DateTimeField(
+ auto_now_add=True, verbose_name='clarification timestamp'
+ ),
+ ),
+ (
+ 'problem',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.Problem',
+ verbose_name='clarified problem',
+ ),
+ ),
],
),
migrations.CreateModel(
name='ProblemData',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('zipfile', models.FileField(blank=True, null=True, storage=judge.utils.problem_data.ProblemDataStorage(), upload_to=judge.models.problem_data.problem_directory_file, verbose_name='data zip file')),
- ('generator', models.FileField(blank=True, null=True, storage=judge.utils.problem_data.ProblemDataStorage(), upload_to=judge.models.problem_data.problem_directory_file, verbose_name='generator file')),
- ('output_prefix', models.IntegerField(blank=True, null=True, verbose_name='output prefix length')),
- ('output_limit', models.IntegerField(blank=True, null=True, verbose_name='output limit length')),
- ('feedback', models.TextField(blank=True, verbose_name='init.yml generation feedback')),
- ('checker', models.CharField(blank=True, choices=[('standard', 'Standard'), ('floats', 'Floats'), ('floatsabs', 'Floats (absolute)'), ('floatsrel', 'Floats (relative)'), ('rstripped', 'Non-trailing spaces'), ('sorted', 'Unordered'), ('identical', 'Byte identical'), ('linecount', 'Line-by-line')], max_length=10, verbose_name='checker')),
- ('checker_args', models.TextField(blank=True, help_text='checker arguments as a JSON object', verbose_name='checker arguments')),
- ('problem', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='data_files', to='judge.Problem', verbose_name='problem')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'zipfile',
+ models.FileField(
+ blank=True,
+ null=True,
+ storage=judge.utils.problem_data.ProblemDataStorage(),
+ upload_to=judge.models.problem_data.problem_directory_file,
+ verbose_name='data zip file',
+ ),
+ ),
+ (
+ 'generator',
+ models.FileField(
+ blank=True,
+ null=True,
+ storage=judge.utils.problem_data.ProblemDataStorage(),
+ upload_to=judge.models.problem_data.problem_directory_file,
+ verbose_name='generator file',
+ ),
+ ),
+ (
+ 'output_prefix',
+ models.IntegerField(
+ blank=True, null=True, verbose_name='output prefix length'
+ ),
+ ),
+ (
+ 'output_limit',
+ models.IntegerField(
+ blank=True, null=True, verbose_name='output limit length'
+ ),
+ ),
+ (
+ 'feedback',
+ models.TextField(
+ blank=True, verbose_name='init.yml generation feedback'
+ ),
+ ),
+ (
+ 'checker',
+ models.CharField(
+ blank=True,
+ choices=[
+ ('standard', 'Standard'),
+ ('floats', 'Floats'),
+ ('floatsabs', 'Floats (absolute)'),
+ ('floatsrel', 'Floats (relative)'),
+ ('rstripped', 'Non-trailing spaces'),
+ ('sorted', 'Unordered'),
+ ('identical', 'Byte identical'),
+ ('linecount', 'Line-by-line'),
+ ],
+ max_length=10,
+ verbose_name='checker',
+ ),
+ ),
+ (
+ 'checker_args',
+ models.TextField(
+ blank=True,
+ help_text='checker arguments as a JSON object',
+ verbose_name='checker arguments',
+ ),
+ ),
+ (
+ 'problem',
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='data_files',
+ to='judge.Problem',
+ verbose_name='problem',
+ ),
+ ),
],
),
migrations.CreateModel(
name='ProblemGroup',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=20, unique=True, verbose_name='problem group ID')),
- ('full_name', models.CharField(max_length=100, verbose_name='problem group name')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(
+ max_length=20, unique=True, verbose_name='problem group ID'
+ ),
+ ),
+ (
+ 'full_name',
+ models.CharField(max_length=100, verbose_name='problem group name'),
+ ),
],
options={
'ordering': ['full_name'],
@@ -397,29 +1434,154 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ProblemTestCase',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('order', models.IntegerField(verbose_name='case position')),
- ('type', models.CharField(choices=[('C', 'Normal case'), ('S', 'Batch start'), ('E', 'Batch end')], default='C', max_length=1, verbose_name='case type')),
- ('input_file', models.CharField(blank=True, max_length=100, verbose_name='input file name')),
- ('output_file', models.CharField(blank=True, max_length=100, verbose_name='output file name')),
- ('generator_args', models.TextField(blank=True, verbose_name='generator arguments')),
- ('points', models.IntegerField(blank=True, null=True, verbose_name='point value')),
+ (
+ 'type',
+ models.CharField(
+ choices=[
+ ('C', 'Normal case'),
+ ('S', 'Batch start'),
+ ('E', 'Batch end'),
+ ],
+ default='C',
+ max_length=1,
+ verbose_name='case type',
+ ),
+ ),
+ (
+ 'input_file',
+ models.CharField(
+ blank=True, max_length=100, verbose_name='input file name'
+ ),
+ ),
+ (
+ 'output_file',
+ models.CharField(
+ blank=True, max_length=100, verbose_name='output file name'
+ ),
+ ),
+ (
+ 'generator_args',
+ models.TextField(blank=True, verbose_name='generator arguments'),
+ ),
+ (
+ 'points',
+ models.IntegerField(
+ blank=True, null=True, verbose_name='point value'
+ ),
+ ),
('is_pretest', models.BooleanField(verbose_name='case is pretest?')),
- ('output_prefix', models.IntegerField(blank=True, null=True, verbose_name='output prefix length')),
- ('output_limit', models.IntegerField(blank=True, null=True, verbose_name='output limit length')),
- ('checker', models.CharField(blank=True, choices=[('standard', 'Standard'), ('floats', 'Floats'), ('floatsabs', 'Floats (absolute)'), ('floatsrel', 'Floats (relative)'), ('rstripped', 'Non-trailing spaces'), ('sorted', 'Unordered'), ('identical', 'Byte identical'), ('linecount', 'Line-by-line')], max_length=10, verbose_name='checker')),
- ('checker_args', models.TextField(blank=True, help_text='checker arguments as a JSON object', verbose_name='checker arguments')),
- ('dataset', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cases', to='judge.Problem', verbose_name='problem data set')),
+ (
+ 'output_prefix',
+ models.IntegerField(
+ blank=True, null=True, verbose_name='output prefix length'
+ ),
+ ),
+ (
+ 'output_limit',
+ models.IntegerField(
+ blank=True, null=True, verbose_name='output limit length'
+ ),
+ ),
+ (
+ 'checker',
+ models.CharField(
+ blank=True,
+ choices=[
+ ('standard', 'Standard'),
+ ('floats', 'Floats'),
+ ('floatsabs', 'Floats (absolute)'),
+ ('floatsrel', 'Floats (relative)'),
+ ('rstripped', 'Non-trailing spaces'),
+ ('sorted', 'Unordered'),
+ ('identical', 'Byte identical'),
+ ('linecount', 'Line-by-line'),
+ ],
+ max_length=10,
+ verbose_name='checker',
+ ),
+ ),
+ (
+ 'checker_args',
+ models.TextField(
+ blank=True,
+ help_text='checker arguments as a JSON object',
+ verbose_name='checker arguments',
+ ),
+ ),
+ (
+ 'dataset',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='cases',
+ to='judge.Problem',
+ verbose_name='problem data set',
+ ),
+ ),
],
),
migrations.CreateModel(
name='ProblemTranslation',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('language', models.CharField(choices=[('de', 'German'), ('en', 'English'), ('es', 'Spanish'), ('fr', 'French'), ('hr', 'Croatian'), ('hu', 'Hungarian'), ('ko', 'Korean'), ('ro', 'Romanian'), ('ru', 'Russian'), ('sr-latn', 'Serbian (Latin)'), ('tr', 'Turkish'), ('vi', 'Vietnamese'), ('zh-hans', 'Simplified Chinese')], max_length=7, verbose_name='language')),
- ('name', models.CharField(db_index=True, max_length=100, verbose_name='translated name')),
- ('description', models.TextField(verbose_name='translated description')),
- ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='judge.Problem', verbose_name='problem')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'language',
+ models.CharField(
+ choices=[
+ ('de', 'German'),
+ ('en', 'English'),
+ ('es', 'Spanish'),
+ ('fr', 'French'),
+ ('hr', 'Croatian'),
+ ('hu', 'Hungarian'),
+ ('ko', 'Korean'),
+ ('ro', 'Romanian'),
+ ('ru', 'Russian'),
+ ('sr-latn', 'Serbian (Latin)'),
+ ('tr', 'Turkish'),
+ ('vi', 'Vietnamese'),
+ ('zh-hans', 'Simplified Chinese'),
+ ],
+ max_length=7,
+ verbose_name='language',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(
+ db_index=True, max_length=100, verbose_name='translated name'
+ ),
+ ),
+ (
+ 'description',
+ models.TextField(verbose_name='translated description'),
+ ),
+ (
+ 'problem',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='translations',
+ to='judge.Problem',
+ verbose_name='problem',
+ ),
+ ),
],
options={
'verbose_name_plural': 'problem translations',
@@ -429,9 +1591,27 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ProblemType',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=20, unique=True, verbose_name='problem category ID')),
- ('full_name', models.CharField(max_length=100, verbose_name='problem category name')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(
+ max_length=20, unique=True, verbose_name='problem category ID'
+ ),
+ ),
+ (
+ 'full_name',
+ models.CharField(
+ max_length=100, verbose_name='problem category name'
+ ),
+ ),
],
options={
'ordering': ['full_name'],
@@ -442,46 +1622,950 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Profile',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('about', models.TextField(blank=True, null=True, verbose_name='self-description')),
- ('timezone', 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/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/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='America/Toronto', max_length=50, verbose_name='location')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'about',
+ models.TextField(
+ blank=True, null=True, verbose_name='self-description'
+ ),
+ ),
+ (
+ 'timezone',
+ 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/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/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='America/Toronto',
+ max_length=50,
+ verbose_name='location',
+ ),
+ ),
('points', models.FloatField(db_index=True, default=0)),
('performance_points', models.FloatField(db_index=True, default=0)),
('problem_count', models.IntegerField(db_index=True, default=0)),
- ('ace_theme', models.CharField(choices=[('ambiance', 'Ambiance'), ('chaos', 'Chaos'), ('chrome', 'Chrome'), ('clouds', 'Clouds'), ('clouds_midnight', 'Clouds Midnight'), ('cobalt', 'Cobalt'), ('crimson_editor', 'Crimson Editor'), ('dawn', 'Dawn'), ('dreamweaver', 'Dreamweaver'), ('eclipse', 'Eclipse'), ('github', 'Github'), ('idle_fingers', 'Idle Fingers'), ('katzenmilch', 'Katzenmilch'), ('kr_theme', 'KR Theme'), ('kuroir', 'Kuroir'), ('merbivore', 'Merbivore'), ('merbivore_soft', 'Merbivore Soft'), ('mono_industrial', 'Mono Industrial'), ('monokai', 'Monokai'), ('pastel_on_dark', 'Pastel on Dark'), ('solarized_dark', 'Solarized Dark'), ('solarized_light', 'Solarized Light'), ('terminal', 'Terminal'), ('textmate', 'Textmate'), ('tomorrow', 'Tomorrow'), ('tomorrow_night', 'Tomorrow Night'), ('tomorrow_night_blue', 'Tomorrow Night Blue'), ('tomorrow_night_bright', 'Tomorrow Night Bright'), ('tomorrow_night_eighties', 'Tomorrow Night Eighties'), ('twilight', 'Twilight'), ('vibrant_ink', 'Vibrant Ink'), ('xcode', 'XCode')], default='github', max_length=30)),
- ('last_access', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last access time')),
- ('ip', models.GenericIPAddressField(blank=True, null=True, verbose_name='last IP')),
- ('display_rank', models.CharField(choices=[('user', 'Normal User'), ('setter', 'Problem Setter'), ('admin', 'Admin')], default='user', max_length=10, verbose_name='display rank')),
- ('mute', models.BooleanField(default=False, help_text='Some users are at their best when silent.', verbose_name='comment mute')),
- ('is_unlisted', models.BooleanField(default=False, help_text='User will not be ranked.', verbose_name='unlisted user')),
+ (
+ 'ace_theme',
+ models.CharField(
+ choices=[
+ ('ambiance', 'Ambiance'),
+ ('chaos', 'Chaos'),
+ ('chrome', 'Chrome'),
+ ('clouds', 'Clouds'),
+ ('clouds_midnight', 'Clouds Midnight'),
+ ('cobalt', 'Cobalt'),
+ ('crimson_editor', 'Crimson Editor'),
+ ('dawn', 'Dawn'),
+ ('dreamweaver', 'Dreamweaver'),
+ ('eclipse', 'Eclipse'),
+ ('github', 'Github'),
+ ('idle_fingers', 'Idle Fingers'),
+ ('katzenmilch', 'Katzenmilch'),
+ ('kr_theme', 'KR Theme'),
+ ('kuroir', 'Kuroir'),
+ ('merbivore', 'Merbivore'),
+ ('merbivore_soft', 'Merbivore Soft'),
+ ('mono_industrial', 'Mono Industrial'),
+ ('monokai', 'Monokai'),
+ ('pastel_on_dark', 'Pastel on Dark'),
+ ('solarized_dark', 'Solarized Dark'),
+ ('solarized_light', 'Solarized Light'),
+ ('terminal', 'Terminal'),
+ ('textmate', 'Textmate'),
+ ('tomorrow', 'Tomorrow'),
+ ('tomorrow_night', 'Tomorrow Night'),
+ ('tomorrow_night_blue', 'Tomorrow Night Blue'),
+ ('tomorrow_night_bright', 'Tomorrow Night Bright'),
+ ('tomorrow_night_eighties', 'Tomorrow Night Eighties'),
+ ('twilight', 'Twilight'),
+ ('vibrant_ink', 'Vibrant Ink'),
+ ('xcode', 'XCode'),
+ ],
+ default='github',
+ max_length=30,
+ ),
+ ),
+ (
+ 'last_access',
+ models.DateTimeField(
+ default=django.utils.timezone.now,
+ verbose_name='last access time',
+ ),
+ ),
+ (
+ 'ip',
+ models.GenericIPAddressField(
+ blank=True, null=True, verbose_name='last IP'
+ ),
+ ),
+ (
+ 'display_rank',
+ models.CharField(
+ choices=[
+ ('user', 'Normal User'),
+ ('setter', 'Problem Setter'),
+ ('admin', 'Admin'),
+ ],
+ default='user',
+ max_length=10,
+ verbose_name='display rank',
+ ),
+ ),
+ (
+ 'mute',
+ models.BooleanField(
+ default=False,
+ help_text='Some users are at their best when silent.',
+ verbose_name='comment mute',
+ ),
+ ),
+ (
+ 'is_unlisted',
+ models.BooleanField(
+ default=False,
+ help_text='User will not be ranked.',
+ verbose_name='unlisted user',
+ ),
+ ),
('rating', models.IntegerField(default=None, null=True)),
- ('user_script', models.TextField(blank=True, default='', help_text='User-defined JavaScript for site customization.', max_length=65536, verbose_name='user script')),
- ('math_engine', models.CharField(choices=[('tex', 'Leave as LaTeX'), ('svg', 'SVG with PNG fallback'), ('mml', 'MathML only'), ('jax', 'MathJax with SVG/PNG fallback'), ('auto', 'Detect best quality')], default='auto', help_text='the rendering engine used to render math', max_length=4, verbose_name='math engine')),
- ('is_totp_enabled', models.BooleanField(default=False, help_text='check to enable TOTP-based two factor authentication', verbose_name='2FA enabled')),
- ('totp_key', judge.models.profile.EncryptedNullCharField(blank=True, help_text='32 character base32-encoded key for TOTP', max_length=32, null=True, validators=[django.core.validators.RegexValidator('^$|^[A-Z2-7]{32}$', 'TOTP key must be empty or base32')], verbose_name='TOTP key')),
- ('notes', models.TextField(blank=True, help_text='Notes for administrators regarding this user.', null=True, verbose_name='internal notes')),
- ('current_contest', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='judge.ContestParticipation', verbose_name='current contest')),
- ('language', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Language', verbose_name='preferred language')),
- ('organizations', sortedm2m.fields.SortedManyToManyField(blank=True, help_text=None, related_name='members', related_query_name='member', to='judge.Organization', verbose_name='organization')),
- ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user associated')),
+ (
+ 'user_script',
+ models.TextField(
+ blank=True,
+ default='',
+ help_text='User-defined JavaScript for site customization.',
+ max_length=65536,
+ verbose_name='user script',
+ ),
+ ),
+ (
+ 'math_engine',
+ models.CharField(
+ choices=[
+ ('tex', 'Leave as LaTeX'),
+ ('svg', 'SVG with PNG fallback'),
+ ('mml', 'MathML only'),
+ ('jax', 'MathJax with SVG/PNG fallback'),
+ ('auto', 'Detect best quality'),
+ ],
+ default='auto',
+ help_text='the rendering engine used to render math',
+ max_length=4,
+ verbose_name='math engine',
+ ),
+ ),
+ (
+ 'is_totp_enabled',
+ models.BooleanField(
+ default=False,
+ help_text='check to enable TOTP-based two factor authentication',
+ verbose_name='2FA enabled',
+ ),
+ ),
+ (
+ 'totp_key',
+ judge.models.profile.EncryptedNullCharField(
+ blank=True,
+ help_text='32 character base32-encoded key for TOTP',
+ max_length=32,
+ null=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^$|^[A-Z2-7]{32}$', 'TOTP key must be empty or base32'
+ )
+ ],
+ verbose_name='TOTP key',
+ ),
+ ),
+ (
+ 'notes',
+ models.TextField(
+ blank=True,
+ help_text='Notes for administrators regarding this user.',
+ null=True,
+ verbose_name='internal notes',
+ ),
+ ),
+ (
+ 'current_contest',
+ models.OneToOneField(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name='+',
+ to='judge.ContestParticipation',
+ verbose_name='current contest',
+ ),
+ ),
+ (
+ 'language',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.Language',
+ verbose_name='preferred language',
+ ),
+ ),
+ (
+ 'organizations',
+ sortedm2m.fields.SortedManyToManyField(
+ blank=True,
+ help_text=None,
+ related_name='members',
+ related_query_name='member',
+ to='judge.Organization',
+ verbose_name='organization',
+ ),
+ ),
+ (
+ 'user',
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ verbose_name='user associated',
+ ),
+ ),
],
options={
'verbose_name_plural': 'user profiles',
- 'permissions': (('test_site', 'Shows in-progress development stuff'), ('totp', 'Edit TOTP settings')),
+ 'permissions': (
+ ('test_site', 'Shows in-progress development stuff'),
+ ('totp', 'Edit TOTP settings'),
+ ),
'verbose_name': 'user profile',
},
),
migrations.CreateModel(
name='Rating',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('rank', models.IntegerField(verbose_name='rank')),
('rating', models.IntegerField(verbose_name='rating')),
('volatility', models.IntegerField(verbose_name='volatility')),
- ('last_rated', models.DateTimeField(db_index=True, verbose_name='last rated')),
- ('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ratings', to='judge.Contest', verbose_name='contest')),
- ('participation', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='rating', to='judge.ContestParticipation', verbose_name='participation')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ratings', to='judge.Profile', verbose_name='user')),
+ (
+ 'last_rated',
+ models.DateTimeField(db_index=True, verbose_name='last rated'),
+ ),
+ (
+ 'contest',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='ratings',
+ to='judge.Contest',
+ verbose_name='contest',
+ ),
+ ),
+ (
+ 'participation',
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='rating',
+ to='judge.ContestParticipation',
+ verbose_name='participation',
+ ),
+ ),
+ (
+ 'user',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='ratings',
+ to='judge.Profile',
+ verbose_name='user',
+ ),
+ ),
],
options={
'verbose_name_plural': 'contest ratings',
@@ -491,23 +2575,83 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='RuntimeVersion',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('name', models.CharField(max_length=64, verbose_name='runtime name')),
- ('version', models.CharField(blank=True, max_length=64, verbose_name='runtime version')),
- ('priority', models.IntegerField(default=0, verbose_name='order in which to display this runtime')),
- ('judge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Judge', verbose_name='judge on which this runtime exists')),
- ('language', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Language', verbose_name='language to which this runtime belongs')),
+ (
+ 'version',
+ models.CharField(
+ blank=True, max_length=64, verbose_name='runtime version'
+ ),
+ ),
+ (
+ 'priority',
+ models.IntegerField(
+ default=0, verbose_name='order in which to display this runtime'
+ ),
+ ),
+ (
+ 'judge',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.Judge',
+ verbose_name='judge on which this runtime exists',
+ ),
+ ),
+ (
+ 'language',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.Language',
+ verbose_name='language to which this runtime belongs',
+ ),
+ ),
],
),
migrations.CreateModel(
name='Solution',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('is_public', models.BooleanField(default=False, verbose_name='public visibility')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'is_public',
+ models.BooleanField(
+ default=False, verbose_name='public visibility'
+ ),
+ ),
('publish_on', models.DateTimeField(verbose_name='publish date')),
('content', models.TextField(verbose_name='editorial content')),
- ('authors', models.ManyToManyField(blank=True, to='judge.Profile', verbose_name='authors')),
- ('problem', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='solution', to='judge.Problem', verbose_name='associated problem')),
+ (
+ 'authors',
+ models.ManyToManyField(
+ blank=True, to='judge.Profile', verbose_name='authors'
+ ),
+ ),
+ (
+ 'problem',
+ models.OneToOneField(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name='solution',
+ to='judge.Problem',
+ verbose_name='associated problem',
+ ),
+ ),
],
options={
'verbose_name_plural': 'solutions',
@@ -518,47 +2662,215 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Submission',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('date', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='submission time')),
- ('time', models.FloatField(db_index=True, null=True, verbose_name='execution time')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'date',
+ models.DateTimeField(
+ auto_now_add=True, db_index=True, verbose_name='submission time'
+ ),
+ ),
+ (
+ 'time',
+ models.FloatField(
+ db_index=True, null=True, verbose_name='execution time'
+ ),
+ ),
('memory', models.FloatField(null=True, verbose_name='memory usage')),
- ('points', models.FloatField(db_index=True, null=True, verbose_name='points granted')),
- ('source', models.TextField(max_length=65536, verbose_name='source code')),
- ('status', models.CharField(choices=[('QU', 'Queued'), ('P', 'Processing'), ('G', 'Grading'), ('D', 'Completed'), ('IE', 'Internal Error'), ('CE', 'Compile Error'), ('AB', 'Aborted')], db_index=True, default='QU', max_length=2, verbose_name='status')),
- ('result', models.CharField(blank=True, choices=[('AC', 'Accepted'), ('WA', 'Wrong Answer'), ('TLE', 'Time Limit Exceeded'), ('MLE', 'Memory Limit Exceeded'), ('OLE', 'Output Limit Exceeded'), ('IR', 'Invalid Return'), ('RTE', 'Runtime Error'), ('CE', 'Compile Error'), ('IE', 'Internal Error'), ('SC', 'Short circuit'), ('AB', 'Aborted')], db_index=True, default=None, max_length=3, null=True, verbose_name='result')),
- ('error', models.TextField(blank=True, null=True, verbose_name='compile errors')),
+ (
+ 'points',
+ models.FloatField(
+ db_index=True, null=True, verbose_name='points granted'
+ ),
+ ),
+ (
+ 'source',
+ models.TextField(max_length=65536, verbose_name='source code'),
+ ),
+ (
+ 'status',
+ models.CharField(
+ choices=[
+ ('QU', 'Queued'),
+ ('P', 'Processing'),
+ ('G', 'Grading'),
+ ('D', 'Completed'),
+ ('IE', 'Internal Error'),
+ ('CE', 'Compile Error'),
+ ('AB', 'Aborted'),
+ ],
+ db_index=True,
+ default='QU',
+ max_length=2,
+ verbose_name='status',
+ ),
+ ),
+ (
+ 'result',
+ models.CharField(
+ blank=True,
+ choices=[
+ ('AC', 'Accepted'),
+ ('WA', 'Wrong Answer'),
+ ('TLE', 'Time Limit Exceeded'),
+ ('MLE', 'Memory Limit Exceeded'),
+ ('OLE', 'Output Limit Exceeded'),
+ ('IR', 'Invalid Return'),
+ ('RTE', 'Runtime Error'),
+ ('CE', 'Compile Error'),
+ ('IE', 'Internal Error'),
+ ('SC', 'Short circuit'),
+ ('AB', 'Aborted'),
+ ],
+ db_index=True,
+ default=None,
+ max_length=3,
+ null=True,
+ verbose_name='result',
+ ),
+ ),
+ (
+ 'error',
+ models.TextField(
+ blank=True, null=True, verbose_name='compile errors'
+ ),
+ ),
('current_testcase', models.IntegerField(default=0)),
- ('batch', models.BooleanField(default=False, verbose_name='batched cases')),
- ('case_points', models.FloatField(default=0, verbose_name='test case points')),
- ('case_total', models.FloatField(default=0, verbose_name='test case total points')),
- ('was_rejudged', models.BooleanField(default=False, verbose_name='was rejudged by admin')),
- ('is_pretested', models.BooleanField(default=False, verbose_name='was ran on pretests only')),
- ('judged_on', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='judge.Judge', verbose_name='judged on')),
- ('language', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Language', verbose_name='submission language')),
- ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Problem')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Profile')),
+ (
+ 'batch',
+ models.BooleanField(default=False, verbose_name='batched cases'),
+ ),
+ (
+ 'case_points',
+ models.FloatField(default=0, verbose_name='test case points'),
+ ),
+ (
+ 'case_total',
+ models.FloatField(default=0, verbose_name='test case total points'),
+ ),
+ (
+ 'was_rejudged',
+ models.BooleanField(
+ default=False, verbose_name='was rejudged by admin'
+ ),
+ ),
+ (
+ 'is_pretested',
+ models.BooleanField(
+ default=False, verbose_name='was ran on pretests only'
+ ),
+ ),
+ (
+ 'judged_on',
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to='judge.Judge',
+ verbose_name='judged on',
+ ),
+ ),
+ (
+ 'language',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.Language',
+ verbose_name='submission language',
+ ),
+ ),
+ (
+ 'problem',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to='judge.Problem'
+ ),
+ ),
+ (
+ 'user',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to='judge.Profile'
+ ),
+ ),
],
options={
'verbose_name_plural': 'submissions',
- 'permissions': (('abort_any_submission', 'Abort any submission'), ('rejudge_submission', 'Rejudge the submission'), ('rejudge_submission_lot', 'Rejudge a lot of submissions'), ('spam_submission', 'Submit without limit'), ('view_all_submission', 'View all submission'), ('resubmit_other', "Resubmit others' submission")),
+ 'permissions': (
+ ('abort_any_submission', 'Abort any submission'),
+ ('rejudge_submission', 'Rejudge the submission'),
+ ('rejudge_submission_lot', 'Rejudge a lot of submissions'),
+ ('spam_submission', 'Submit without limit'),
+ ('view_all_submission', 'View all submission'),
+ ('resubmit_other', "Resubmit others' submission"),
+ ),
'verbose_name': 'submission',
},
),
migrations.CreateModel(
name='SubmissionTestCase',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('case', models.IntegerField(verbose_name='test case ID')),
- ('status', models.CharField(choices=[('AC', 'Accepted'), ('WA', 'Wrong Answer'), ('TLE', 'Time Limit Exceeded'), ('MLE', 'Memory Limit Exceeded'), ('OLE', 'Output Limit Exceeded'), ('IR', 'Invalid Return'), ('RTE', 'Runtime Error'), ('CE', 'Compile Error'), ('IE', 'Internal Error'), ('SC', 'Short circuit'), ('AB', 'Aborted')], max_length=3, verbose_name='status flag')),
+ (
+ 'status',
+ models.CharField(
+ choices=[
+ ('AC', 'Accepted'),
+ ('WA', 'Wrong Answer'),
+ ('TLE', 'Time Limit Exceeded'),
+ ('MLE', 'Memory Limit Exceeded'),
+ ('OLE', 'Output Limit Exceeded'),
+ ('IR', 'Invalid Return'),
+ ('RTE', 'Runtime Error'),
+ ('CE', 'Compile Error'),
+ ('IE', 'Internal Error'),
+ ('SC', 'Short circuit'),
+ ('AB', 'Aborted'),
+ ],
+ max_length=3,
+ verbose_name='status flag',
+ ),
+ ),
('time', models.FloatField(null=True, verbose_name='execution time')),
('memory', models.FloatField(null=True, verbose_name='memory usage')),
('points', models.FloatField(null=True, verbose_name='points granted')),
('total', models.FloatField(null=True, verbose_name='points possible')),
('batch', models.IntegerField(null=True, verbose_name='batch number')),
- ('feedback', models.CharField(blank=True, max_length=50, verbose_name='judging feedback')),
- ('extended_feedback', models.TextField(blank=True, verbose_name='extended judging feedback')),
+ (
+ 'feedback',
+ models.CharField(
+ blank=True, max_length=50, verbose_name='judging feedback'
+ ),
+ ),
+ (
+ 'extended_feedback',
+ models.TextField(
+ blank=True, verbose_name='extended judging feedback'
+ ),
+ ),
('output', models.TextField(blank=True, verbose_name='program output')),
- ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='test_cases', to='judge.Submission', verbose_name='associated submission')),
+ (
+ 'submission',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='test_cases',
+ to='judge.Submission',
+ verbose_name='associated submission',
+ ),
+ ),
],
options={
'verbose_name_plural': 'submission test cases',
@@ -568,171 +2880,385 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Ticket',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('title', models.CharField(max_length=100, verbose_name='ticket title')),
- ('time', models.DateTimeField(auto_now_add=True, verbose_name='creation time')),
- ('notes', models.TextField(blank=True, help_text='Staff notes for this issue to aid in processing.', verbose_name='quick notes')),
- ('object_id', models.PositiveIntegerField(verbose_name='linked item ID')),
- ('is_open', models.BooleanField(default=True, verbose_name='is ticket open?')),
- ('assignees', models.ManyToManyField(related_name='assigned_tickets', to='judge.Profile', verbose_name='assignees')),
- ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType', verbose_name='linked item type')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to='judge.Profile', verbose_name='ticket creator')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'title',
+ models.CharField(max_length=100, verbose_name='ticket title'),
+ ),
+ (
+ 'time',
+ models.DateTimeField(
+ auto_now_add=True, verbose_name='creation time'
+ ),
+ ),
+ (
+ 'notes',
+ models.TextField(
+ blank=True,
+ help_text='Staff notes for this issue to aid in processing.',
+ verbose_name='quick notes',
+ ),
+ ),
+ (
+ 'object_id',
+ models.PositiveIntegerField(verbose_name='linked item ID'),
+ ),
+ (
+ 'is_open',
+ models.BooleanField(default=True, verbose_name='is ticket open?'),
+ ),
+ (
+ 'assignees',
+ models.ManyToManyField(
+ related_name='assigned_tickets',
+ to='judge.Profile',
+ verbose_name='assignees',
+ ),
+ ),
+ (
+ 'content_type',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='contenttypes.ContentType',
+ verbose_name='linked item type',
+ ),
+ ),
+ (
+ 'user',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='tickets',
+ to='judge.Profile',
+ verbose_name='ticket creator',
+ ),
+ ),
],
),
migrations.CreateModel(
name='TicketMessage',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('body', models.TextField(verbose_name='message body')),
- ('time', models.DateTimeField(auto_now_add=True, verbose_name='message time')),
- ('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', related_query_name='message', to='judge.Ticket', verbose_name='ticket')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_messages', to='judge.Profile', verbose_name='poster')),
+ (
+ 'time',
+ models.DateTimeField(
+ auto_now_add=True, verbose_name='message time'
+ ),
+ ),
+ (
+ 'ticket',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='messages',
+ related_query_name='message',
+ to='judge.Ticket',
+ verbose_name='ticket',
+ ),
+ ),
+ (
+ 'user',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='ticket_messages',
+ to='judge.Profile',
+ verbose_name='poster',
+ ),
+ ),
],
),
migrations.AddField(
model_name='problem',
name='authors',
- field=models.ManyToManyField(blank=True, related_name='authored_problems', to='judge.Profile', verbose_name='creators'),
+ field=models.ManyToManyField(
+ blank=True,
+ related_name='authored_problems',
+ to='judge.Profile',
+ verbose_name='creators',
+ ),
),
migrations.AddField(
model_name='problem',
name='banned_users',
- field=models.ManyToManyField(blank=True, help_text='Bans the selected users from submitting to this problem.', to='judge.Profile', verbose_name='personae non gratae'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='Bans the selected users from submitting to this problem.',
+ to='judge.Profile',
+ verbose_name='personae non gratae',
+ ),
),
migrations.AddField(
model_name='problem',
name='curators',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to edit a problem, but not be publicly shown as an author.', related_name='curated_problems', to='judge.Profile', verbose_name='curators'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to edit a problem, but not be publicly shown as an author.',
+ related_name='curated_problems',
+ to='judge.Profile',
+ verbose_name='curators',
+ ),
),
migrations.AddField(
model_name='problem',
name='group',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.ProblemGroup', verbose_name='problem group'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.ProblemGroup',
+ verbose_name='problem group',
+ ),
),
migrations.AddField(
model_name='problem',
name='license',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='judge.License'),
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to='judge.License',
+ ),
),
migrations.AddField(
model_name='problem',
name='organizations',
- field=models.ManyToManyField(blank=True, help_text='If private, only these organizations may see the problem.', to='judge.Organization', verbose_name='organizations'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='If private, only these organizations may see the problem.',
+ to='judge.Organization',
+ verbose_name='organizations',
+ ),
),
migrations.AddField(
model_name='problem',
name='testers',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to view a private problem, but not edit it.', related_name='tested_problems', to='judge.Profile', verbose_name='testers'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to view a private problem, but not edit it.',
+ related_name='tested_problems',
+ to='judge.Profile',
+ verbose_name='testers',
+ ),
),
migrations.AddField(
model_name='problem',
name='types',
- field=models.ManyToManyField(to='judge.ProblemType', verbose_name='problem types'),
+ field=models.ManyToManyField(
+ to='judge.ProblemType', verbose_name='problem types'
+ ),
),
migrations.AddField(
model_name='privatemessage',
name='sender',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to='judge.Profile', verbose_name='sender'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='sent_messages',
+ to='judge.Profile',
+ verbose_name='sender',
+ ),
),
migrations.AddField(
model_name='privatemessage',
name='target',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to='judge.Profile', verbose_name='target'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='received_messages',
+ to='judge.Profile',
+ verbose_name='target',
+ ),
),
migrations.AddField(
model_name='organizationrequest',
name='user',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='requests', to='judge.Profile', verbose_name='user'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='requests',
+ to='judge.Profile',
+ verbose_name='user',
+ ),
),
migrations.AddField(
model_name='organization',
name='admins',
- field=models.ManyToManyField(help_text='Those who can edit this organization', related_name='admin_of', to='judge.Profile', verbose_name='administrators'),
+ field=models.ManyToManyField(
+ help_text='Those who can edit this organization',
+ related_name='admin_of',
+ to='judge.Profile',
+ verbose_name='administrators',
+ ),
),
migrations.AddField(
model_name='organization',
name='registrant',
- field=models.ForeignKey(help_text='User who registered this organization', on_delete=django.db.models.deletion.CASCADE, related_name='registrant+', to='judge.Profile', verbose_name='registrant'),
+ field=models.ForeignKey(
+ help_text='User who registered this organization',
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='registrant+',
+ to='judge.Profile',
+ verbose_name='registrant',
+ ),
),
migrations.AddField(
model_name='languagelimit',
name='problem',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='language_limits', to='judge.Problem', verbose_name='problem'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='language_limits',
+ to='judge.Problem',
+ verbose_name='problem',
+ ),
),
migrations.AddField(
model_name='judge',
name='problems',
- field=models.ManyToManyField(related_name='judges', to='judge.Problem', verbose_name='problems'),
+ field=models.ManyToManyField(
+ related_name='judges', to='judge.Problem', verbose_name='problems'
+ ),
),
migrations.AddField(
model_name='judge',
name='runtimes',
- field=models.ManyToManyField(related_name='judges', to='judge.Language', verbose_name='judges'),
+ field=models.ManyToManyField(
+ related_name='judges', to='judge.Language', verbose_name='judges'
+ ),
),
migrations.AddField(
model_name='contestsubmission',
name='submission',
- field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='contest', to='judge.Submission', verbose_name='submission'),
+ field=models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='contest',
+ to='judge.Submission',
+ verbose_name='submission',
+ ),
),
migrations.AddField(
model_name='contestproblem',
name='problem',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contests', to='judge.Problem', verbose_name='problem'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='contests',
+ to='judge.Problem',
+ verbose_name='problem',
+ ),
),
migrations.AddField(
model_name='contestparticipation',
name='user',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contest_history', to='judge.Profile', verbose_name='user'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='contest_history',
+ to='judge.Profile',
+ verbose_name='user',
+ ),
),
migrations.AddField(
model_name='contest',
name='banned_users',
- field=models.ManyToManyField(blank=True, help_text='Bans the selected users from joining this contest.', to='judge.Profile', verbose_name='personae non gratae'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='Bans the selected users from joining this contest.',
+ to='judge.Profile',
+ verbose_name='personae non gratae',
+ ),
),
migrations.AddField(
model_name='contest',
name='organizations',
- field=models.ManyToManyField(blank=True, help_text='If private, only these organizations may see the contest', to='judge.Organization', verbose_name='organizations'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='If private, only these organizations may see the contest',
+ to='judge.Organization',
+ verbose_name='organizations',
+ ),
),
migrations.AddField(
model_name='contest',
name='organizers',
- field=models.ManyToManyField(help_text='These people will be able to edit the contest.', related_name='_contest_organizers_+', to='judge.Profile'),
+ field=models.ManyToManyField(
+ help_text='These people will be able to edit the contest.',
+ related_name='_contest_organizers_+',
+ to='judge.Profile',
+ ),
),
migrations.AddField(
model_name='contest',
name='problems',
- field=models.ManyToManyField(through='judge.ContestProblem', to='judge.Problem', verbose_name='problems'),
+ field=models.ManyToManyField(
+ through='judge.ContestProblem',
+ to='judge.Problem',
+ verbose_name='problems',
+ ),
),
migrations.AddField(
model_name='contest',
name='rate_exclude',
- field=models.ManyToManyField(blank=True, related_name='_contest_rate_exclude_+', to='judge.Profile', verbose_name='exclude from ratings'),
+ field=models.ManyToManyField(
+ blank=True,
+ related_name='_contest_rate_exclude_+',
+ to='judge.Profile',
+ verbose_name='exclude from ratings',
+ ),
),
migrations.AddField(
model_name='contest',
name='tags',
- field=models.ManyToManyField(blank=True, related_name='contests', to='judge.ContestTag', verbose_name='contest tags'),
+ field=models.ManyToManyField(
+ blank=True,
+ related_name='contests',
+ to='judge.ContestTag',
+ verbose_name='contest tags',
+ ),
),
migrations.AddField(
model_name='commentvote',
name='voter',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='voted_comments', to='judge.Profile'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='voted_comments',
+ to='judge.Profile',
+ ),
),
migrations.AddField(
model_name='comment',
name='author',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.Profile', verbose_name='commenter'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.Profile',
+ verbose_name='commenter',
+ ),
),
migrations.AddField(
model_name='comment',
name='parent',
- field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='judge.Comment', verbose_name='parent'),
+ field=mptt.fields.TreeForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='replies',
+ to='judge.Comment',
+ verbose_name='parent',
+ ),
),
migrations.AddField(
model_name='blogpost',
name='authors',
- field=models.ManyToManyField(blank=True, to='judge.Profile', verbose_name='authors'),
+ field=models.ManyToManyField(
+ blank=True, to='judge.Profile', verbose_name='authors'
+ ),
),
migrations.AlterUniqueTogether(
name='rating',
diff --git a/judge/migrations/0085_submission_source.py b/judge/migrations/0085_submission_source.py
index 5bfa72acb8..a50a9b9ac7 100644
--- a/judge/migrations/0085_submission_source.py
+++ b/judge/migrations/0085_submission_source.py
@@ -5,7 +5,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0001_squashed_0084_contest_formats'),
]
@@ -14,17 +13,40 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='SubmissionSource',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('source', models.TextField(max_length=65536, verbose_name='source code')),
- ('submission', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='link', to='judge.Submission', verbose_name='associated submission')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'source',
+ models.TextField(max_length=65536, verbose_name='source code'),
+ ),
+ (
+ 'submission',
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='link',
+ to='judge.Submission',
+ verbose_name='associated submission',
+ ),
+ ),
],
),
migrations.RunSQL(
- ["""INSERT INTO judge_submissionsource (source, submission_id)
- SELECT source, id AS 'submission_id' FROM judge_submission;"""],
- ["""UPDATE judge_submission sub
+ [
+ """INSERT INTO judge_submissionsource (source, submission_id)
+ SELECT source, id AS 'submission_id' FROM judge_submission;"""
+ ],
+ [
+ """UPDATE judge_submission sub
INNER JOIN judge_submissionsource src ON sub.id = src.submission_id
- SET sub.source = src.source;"""],
+ SET sub.source = src.source;"""
+ ],
elidable=True,
),
migrations.RemoveField(
@@ -34,6 +56,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='submissionsource',
name='submission',
- field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='source', to='judge.Submission', verbose_name='associated submission'),
+ field=models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='source',
+ to='judge.Submission',
+ verbose_name='associated submission',
+ ),
),
]
diff --git a/judge/migrations/0086_rating_ceiling.py b/judge/migrations/0086_rating_ceiling.py
index a544a21972..a20d61662c 100644
--- a/judge/migrations/0086_rating_ceiling.py
+++ b/judge/migrations/0086_rating_ceiling.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0085_submission_source'),
]
@@ -13,11 +12,21 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='rating_ceiling',
- field=models.IntegerField(blank=True, help_text='Rating ceiling for contest', null=True, verbose_name='rating ceiling'),
+ field=models.IntegerField(
+ blank=True,
+ help_text='Rating ceiling for contest',
+ null=True,
+ verbose_name='rating ceiling',
+ ),
),
migrations.AddField(
model_name='contest',
name='rating_floor',
- field=models.IntegerField(blank=True, help_text='Rating floor for contest', null=True, verbose_name='rating floor'),
+ field=models.IntegerField(
+ blank=True,
+ help_text='Rating floor for contest',
+ null=True,
+ verbose_name='rating floor',
+ ),
),
]
diff --git a/judge/migrations/0087_problem_resource_limits.py b/judge/migrations/0087_problem_resource_limits.py
index e6da153fcb..8f44aba3d4 100644
--- a/judge/migrations/0087_problem_resource_limits.py
+++ b/judge/migrations/0087_problem_resource_limits.py
@@ -5,7 +5,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0086_rating_ceiling'),
]
@@ -14,11 +13,21 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='problem',
name='memory_limit',
- field=models.PositiveIntegerField(help_text='The memory limit for this problem, in kilobytes (e.g. 64mb = 65536 kilobytes).', verbose_name='memory limit'),
+ field=models.PositiveIntegerField(
+ help_text='The memory limit for this problem, in kilobytes (e.g. 64mb = 65536 kilobytes).',
+ verbose_name='memory limit',
+ ),
),
migrations.AlterField(
model_name='problem',
name='time_limit',
- field=models.FloatField(help_text='The time limit for this problem, in seconds. Fractional seconds (e.g. 1.5) are supported.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(2000)], verbose_name='time limit'),
+ field=models.FloatField(
+ help_text='The time limit for this problem, in seconds. Fractional seconds (e.g. 1.5) are supported.',
+ validators=[
+ django.core.validators.MinValueValidator(0),
+ django.core.validators.MaxValueValidator(2000),
+ ],
+ verbose_name='time limit',
+ ),
),
]
diff --git a/judge/migrations/0088_private_contests.py b/judge/migrations/0088_private_contests.py
index b3505b554d..fcfa1ec37a 100644
--- a/judge/migrations/0088_private_contests.py
+++ b/judge/migrations/0088_private_contests.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0087_problem_resource_limits'),
]
@@ -12,7 +11,18 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='contest',
- options={'permissions': (('see_private_contest', 'See private contests'), ('edit_own_contest', 'Edit own contests'), ('edit_all_contest', 'Edit all contests'), ('contest_rating', 'Rate contests'), ('contest_access_code', 'Contest access codes'), ('create_private_contest', 'Create private contests')), 'verbose_name': 'contest', 'verbose_name_plural': 'contests'},
+ options={
+ 'permissions': (
+ ('see_private_contest', 'See private contests'),
+ ('edit_own_contest', 'Edit own contests'),
+ ('edit_all_contest', 'Edit all contests'),
+ ('contest_rating', 'Rate contests'),
+ ('contest_access_code', 'Contest access codes'),
+ ('create_private_contest', 'Create private contests'),
+ ),
+ 'verbose_name': 'contest',
+ 'verbose_name_plural': 'contests',
+ },
),
migrations.RenameField(
model_name='contest',
@@ -22,16 +32,26 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='is_organization_private',
- field=models.BooleanField(default=False, verbose_name='private to organizations'),
+ field=models.BooleanField(
+ default=False, verbose_name='private to organizations'
+ ),
),
migrations.AddField(
model_name='contest',
name='private_contestants',
- field=models.ManyToManyField(blank=True, help_text='If private, only these users may see the contest', related_name='_contest_private_contestants_+', to='judge.Profile', verbose_name='private contestants'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='If private, only these users may see the contest',
+ related_name='_contest_private_contestants_+',
+ to='judge.Profile',
+ verbose_name='private contestants',
+ ),
),
migrations.AlterField(
model_name='contest',
name='is_private',
- field=models.BooleanField(default=False, verbose_name='private to specific users'),
+ field=models.BooleanField(
+ default=False, verbose_name='private to specific users'
+ ),
),
]
diff --git a/judge/migrations/0089_submission_to_contest.py b/judge/migrations/0089_submission_to_contest.py
index 73fe6cb50a..19cf8840c7 100644
--- a/judge/migrations/0089_submission_to_contest.py
+++ b/judge/migrations/0089_submission_to_contest.py
@@ -5,7 +5,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0088_private_contests'),
]
@@ -14,14 +13,24 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='submission',
name='contest_object',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='judge.Contest', verbose_name='contest'),
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name='+',
+ to='judge.Contest',
+ verbose_name='contest',
+ ),
),
- migrations.RunSQL("""
+ migrations.RunSQL(
+ """
UPDATE `judge_submission`
INNER JOIN `judge_contestsubmission`
ON (`judge_submission`.`id` = `judge_contestsubmission`.`submission_id`)
INNER JOIN `judge_contestparticipation`
ON (`judge_contestsubmission`.`participation_id` = `judge_contestparticipation`.`id`)
SET `judge_submission`.`contest_object_id` = `judge_contestparticipation`.`contest_id`
- """, migrations.RunSQL.noop),
+ """,
+ migrations.RunSQL.noop,
+ ),
]
diff --git a/judge/migrations/0090_fix_contest_visibility.py b/judge/migrations/0090_fix_contest_visibility.py
index 03c6b74e7b..979087a417 100644
--- a/judge/migrations/0090_fix_contest_visibility.py
+++ b/judge/migrations/0090_fix_contest_visibility.py
@@ -2,18 +2,20 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0089_submission_to_contest'),
]
operations = [
- migrations.RunSQL("""
+ migrations.RunSQL(
+ """
UPDATE `judge_contest`
SET `judge_contest`.`is_private` = 0, `judge_contest`.`is_organization_private` = 1
WHERE `judge_contest`.`is_private` = 1
- """, """
+ """,
+ """
UPDATE `judge_contest`
SET `judge_contest`.`is_private` = `judge_contest`.`is_organization_private`
- """),
+ """,
+ ),
]
diff --git a/judge/migrations/0091_compiler_message_ansi2html.py b/judge/migrations/0091_compiler_message_ansi2html.py
index 7240f95212..10a439b110 100644
--- a/judge/migrations/0091_compiler_message_ansi2html.py
+++ b/judge/migrations/0091_compiler_message_ansi2html.py
@@ -13,7 +13,6 @@ def strip_error_html(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0090_fix_contest_visibility'),
]
diff --git a/judge/migrations/0092_contest_clone.py b/judge/migrations/0092_contest_clone.py
index 0235513a68..4d72882009 100644
--- a/judge/migrations/0092_contest_clone.py
+++ b/judge/migrations/0092_contest_clone.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0091_compiler_message_ansi2html'),
]
@@ -12,6 +11,18 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='contest',
- options={'permissions': (('see_private_contest', 'See private contests'), ('edit_own_contest', 'Edit own contests'), ('edit_all_contest', 'Edit all contests'), ('clone_contest', 'Clone contest'), ('contest_rating', 'Rate contests'), ('contest_access_code', 'Contest access codes'), ('create_private_contest', 'Create private contests')), 'verbose_name': 'contest', 'verbose_name_plural': 'contests'},
+ options={
+ 'permissions': (
+ ('see_private_contest', 'See private contests'),
+ ('edit_own_contest', 'Edit own contests'),
+ ('edit_all_contest', 'Edit all contests'),
+ ('clone_contest', 'Clone contest'),
+ ('contest_rating', 'Rate contests'),
+ ('contest_access_code', 'Contest access codes'),
+ ('create_private_contest', 'Create private contests'),
+ ),
+ 'verbose_name': 'contest',
+ 'verbose_name_plural': 'contests',
+ },
),
]
diff --git a/judge/migrations/0093_contest_moss.py b/judge/migrations/0093_contest_moss.py
index 10e2799561..a99a270710 100644
--- a/judge/migrations/0093_contest_moss.py
+++ b/judge/migrations/0093_contest_moss.py
@@ -5,7 +5,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0092_contest_clone'),
]
@@ -14,7 +13,15 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ContestMoss',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('language', models.CharField(max_length=10)),
('submission_count', models.PositiveIntegerField(default=0)),
('url', models.URLField(blank=True, null=True)),
@@ -26,17 +33,40 @@ class Migration(migrations.Migration):
),
migrations.AlterModelOptions(
name='contest',
- options={'permissions': (('see_private_contest', 'See private contests'), ('edit_own_contest', 'Edit own contests'), ('edit_all_contest', 'Edit all contests'), ('clone_contest', 'Clone contest'), ('moss_contest', 'MOSS contest'), ('contest_rating', 'Rate contests'), ('contest_access_code', 'Contest access codes'), ('create_private_contest', 'Create private contests')), 'verbose_name': 'contest', 'verbose_name_plural': 'contests'},
+ options={
+ 'permissions': (
+ ('see_private_contest', 'See private contests'),
+ ('edit_own_contest', 'Edit own contests'),
+ ('edit_all_contest', 'Edit all contests'),
+ ('clone_contest', 'Clone contest'),
+ ('moss_contest', 'MOSS contest'),
+ ('contest_rating', 'Rate contests'),
+ ('contest_access_code', 'Contest access codes'),
+ ('create_private_contest', 'Create private contests'),
+ ),
+ 'verbose_name': 'contest',
+ 'verbose_name_plural': 'contests',
+ },
),
migrations.AddField(
model_name='contestmoss',
name='contest',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='moss', to='judge.Contest', verbose_name='contest'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='moss',
+ to='judge.Contest',
+ verbose_name='contest',
+ ),
),
migrations.AddField(
model_name='contestmoss',
name='problem',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='moss', to='judge.Problem', verbose_name='problem'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='moss',
+ to='judge.Problem',
+ verbose_name='problem',
+ ),
),
migrations.AlterUniqueTogether(
name='contestmoss',
diff --git a/judge/migrations/0095_organization_logo_override.py b/judge/migrations/0095_organization_logo_override.py
index b1d86eb24b..12942d79b2 100644
--- a/judge/migrations/0095_organization_logo_override.py
+++ b/judge/migrations/0095_organization_logo_override.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0094_submissiontestcase_unique_together'),
]
@@ -13,6 +12,12 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='organization',
name='logo_override_image',
- field=models.CharField(blank=True, default='', help_text='This image will replace the default site logo for users viewing the organization.', max_length=150, verbose_name='Logo override image'),
+ field=models.CharField(
+ blank=True,
+ default='',
+ help_text='This image will replace the default site logo for users viewing the organization.',
+ max_length=150,
+ verbose_name='Logo override image',
+ ),
),
]
diff --git a/judge/migrations/0096_profile_language_set_default.py b/judge/migrations/0096_profile_language_set_default.py
index a0a9453de6..dae3db3353 100644
--- a/judge/migrations/0096_profile_language_set_default.py
+++ b/judge/migrations/0096_profile_language_set_default.py
@@ -12,7 +12,6 @@ def create_python3(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0095_organization_logo_override'),
]
@@ -22,6 +21,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='profile',
name='language',
- field=models.ForeignKey(default=judge.models.runtime.Language.get_default_language_pk, on_delete=django.db.models.deletion.SET_DEFAULT, to='judge.Language', verbose_name='preferred language'),
+ field=models.ForeignKey(
+ default=judge.models.runtime.Language.get_default_language_pk,
+ on_delete=django.db.models.deletion.SET_DEFAULT,
+ to='judge.Language',
+ verbose_name='preferred language',
+ ),
),
]
diff --git a/judge/migrations/0097_participation_is_disqualified.py b/judge/migrations/0097_participation_is_disqualified.py
index 537f83f0be..36b6f8c324 100644
--- a/judge/migrations/0097_participation_is_disqualified.py
+++ b/judge/migrations/0097_participation_is_disqualified.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0096_profile_language_set_default'),
]
@@ -13,11 +12,19 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contestparticipation',
name='is_disqualified',
- field=models.BooleanField(default=False, help_text='Whether this participation is disqualified.', verbose_name='is disqualified'),
+ field=models.BooleanField(
+ default=False,
+ help_text='Whether this participation is disqualified.',
+ verbose_name='is disqualified',
+ ),
),
migrations.AlterField(
model_name='contestparticipation',
name='virtual',
- field=models.IntegerField(default=0, help_text='0 means non-virtual, otherwise the n-th virtual participation.', verbose_name='virtual participation id'),
+ field=models.IntegerField(
+ default=0,
+ help_text='0 means non-virtual, otherwise the n-th virtual participation.',
+ verbose_name='virtual participation id',
+ ),
),
]
diff --git a/judge/migrations/0098_view_contest_scoreboard.py b/judge/migrations/0098_view_contest_scoreboard.py
index ae000b1217..4e704b9534 100644
--- a/judge/migrations/0098_view_contest_scoreboard.py
+++ b/judge/migrations/0098_view_contest_scoreboard.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0097_participation_is_disqualified'),
]
@@ -13,6 +12,12 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='view_contest_scoreboard',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to view the scoreboard.', related_name='view_contest_scoreboard', to='judge.Profile', verbose_name='view contest scoreboard'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to view the scoreboard.',
+ related_name='view_contest_scoreboard',
+ to='judge.Profile',
+ verbose_name='view contest scoreboard',
+ ),
),
]
diff --git a/judge/migrations/0099_contest_problem_label.py b/judge/migrations/0099_contest_problem_label.py
index 4bd5db7a4a..18f51661c2 100644
--- a/judge/migrations/0099_contest_problem_label.py
+++ b/judge/migrations/0099_contest_problem_label.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0098_view_contest_scoreboard'),
]
@@ -13,10 +12,28 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='problem_label_script',
- field=models.TextField(blank=True, help_text='A custom Lua function to generate problem labels. Requires a single function with an integer parameter, the zero-indexed contest problem index, and returns a string, the label.', verbose_name='contest problem label script'),
+ field=models.TextField(
+ blank=True,
+ help_text='A custom Lua function to generate problem labels. Requires a single function with an integer parameter, the zero-indexed contest problem index, and returns a string, the label.',
+ verbose_name='contest problem label script',
+ ),
),
migrations.AlterModelOptions(
name='contest',
- options={'permissions': (('see_private_contest', 'See private contests'), ('edit_own_contest', 'Edit own contests'), ('edit_all_contest', 'Edit all contests'), ('clone_contest', 'Clone contest'), ('moss_contest', 'MOSS contest'), ('contest_rating', 'Rate contests'), ('contest_access_code', 'Contest access codes'), ('create_private_contest', 'Create private contests'), ('contest_problem_label', 'Edit contest problem label script')), 'verbose_name': 'contest', 'verbose_name_plural': 'contests'},
+ options={
+ 'permissions': (
+ ('see_private_contest', 'See private contests'),
+ ('edit_own_contest', 'Edit own contests'),
+ ('edit_all_contest', 'Edit all contests'),
+ ('clone_contest', 'Clone contest'),
+ ('moss_contest', 'MOSS contest'),
+ ('contest_rating', 'Rate contests'),
+ ('contest_access_code', 'Contest access codes'),
+ ('create_private_contest', 'Create private contests'),
+ ('contest_problem_label', 'Edit contest problem label script'),
+ ),
+ 'verbose_name': 'contest',
+ 'verbose_name_plural': 'contests',
+ },
),
]
diff --git a/judge/migrations/0100_contest_visiblity_permission.py b/judge/migrations/0100_contest_visiblity_permission.py
index 784ed60b91..d01a2a05af 100644
--- a/judge/migrations/0100_contest_visiblity_permission.py
+++ b/judge/migrations/0100_contest_visiblity_permission.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0099_contest_problem_label'),
]
@@ -12,6 +11,21 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='contest',
- options={'permissions': (('see_private_contest', 'See private contests'), ('edit_own_contest', 'Edit own contests'), ('edit_all_contest', 'Edit all contests'), ('clone_contest', 'Clone contest'), ('moss_contest', 'MOSS contest'), ('contest_rating', 'Rate contests'), ('contest_access_code', 'Contest access codes'), ('create_private_contest', 'Create private contests'), ('contest_problem_label', 'Edit contest problem label script'), ('change_contest_visibility', 'Change contest visibility')), 'verbose_name': 'contest', 'verbose_name_plural': 'contests'},
+ options={
+ 'permissions': (
+ ('see_private_contest', 'See private contests'),
+ ('edit_own_contest', 'Edit own contests'),
+ ('edit_all_contest', 'Edit all contests'),
+ ('clone_contest', 'Clone contest'),
+ ('moss_contest', 'MOSS contest'),
+ ('contest_rating', 'Rate contests'),
+ ('contest_access_code', 'Contest access codes'),
+ ('create_private_contest', 'Create private contests'),
+ ('contest_problem_label', 'Edit contest problem label script'),
+ ('change_contest_visibility', 'Change contest visibility'),
+ ),
+ 'verbose_name': 'contest',
+ 'verbose_name_plural': 'contests',
+ },
),
]
diff --git a/judge/migrations/0101_submission_judged_date.py b/judge/migrations/0101_submission_judged_date.py
index cac63688fc..9394f9d75a 100644
--- a/judge/migrations/0101_submission_judged_date.py
+++ b/judge/migrations/0101_submission_judged_date.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0100_contest_visiblity_permission'),
]
@@ -13,6 +12,8 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='submission',
name='judged_date',
- field=models.DateTimeField(default=None, null=True, verbose_name='submission judge time'),
+ field=models.DateTimeField(
+ default=None, null=True, verbose_name='submission judge time'
+ ),
),
]
diff --git a/judge/migrations/0102_api_token.py b/judge/migrations/0102_api_token.py
index 1e4c2da9cd..403e16e992 100644
--- a/judge/migrations/0102_api_token.py
+++ b/judge/migrations/0102_api_token.py
@@ -5,7 +5,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0101_submission_judged_date'),
]
@@ -14,6 +13,16 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='profile',
name='api_token',
- field=models.CharField(help_text='64 character hex-encoded API access token', max_length=64, null=True, validators=[django.core.validators.RegexValidator('^[a-f0-9]{64}$', 'API token must be None or hexadecimal')], verbose_name='API token'),
+ field=models.CharField(
+ help_text='64 character hex-encoded API access token',
+ max_length=64,
+ null=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[a-f0-9]{64}$', 'API token must be None or hexadecimal'
+ )
+ ],
+ verbose_name='API token',
+ ),
),
]
diff --git a/judge/migrations/0103_contest_participation_tiebreak_field.py b/judge/migrations/0103_contest_participation_tiebreak_field.py
index d11bce272a..f2e99f1cb9 100644
--- a/judge/migrations/0103_contest_participation_tiebreak_field.py
+++ b/judge/migrations/0103_contest_participation_tiebreak_field.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0102_api_token'),
]
diff --git a/judge/migrations/0104_contestproblem_maxsubs.py b/judge/migrations/0104_contestproblem_maxsubs.py
index 776d7bc026..188592c5e4 100644
--- a/judge/migrations/0104_contestproblem_maxsubs.py
+++ b/judge/migrations/0104_contestproblem_maxsubs.py
@@ -16,7 +16,6 @@ def none_to_zero(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0103_contest_participation_tiebreak_field'),
]
@@ -25,7 +24,17 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contestproblem',
name='max_submissions',
- field=models.IntegerField(blank=True, default=None, help_text='Maximum number of submissions for this problem, or leave blank for no limit.', null=True, validators=[judge.models.contest.MinValueOrNoneValidator(1, "Why include a problem you can't submit to?")]),
+ field=models.IntegerField(
+ blank=True,
+ default=None,
+ help_text='Maximum number of submissions for this problem, or leave blank for no limit.',
+ null=True,
+ validators=[
+ judge.models.contest.MinValueOrNoneValidator(
+ 1, "Why include a problem you can't submit to?"
+ )
+ ],
+ ),
),
migrations.RunPython(zero_to_none, none_to_zero, atomic=True),
]
diff --git a/judge/migrations/0105_webauthn.py b/judge/migrations/0105_webauthn.py
index 4bf2289188..d0f24ef939 100644
--- a/judge/migrations/0105_webauthn.py
+++ b/judge/migrations/0105_webauthn.py
@@ -5,7 +5,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0104_contestproblem_maxsubs'),
]
@@ -14,17 +13,42 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='profile',
name='is_webauthn_enabled',
- field=models.BooleanField(default=False, help_text='check to enable WebAuthn-based two-factor authentication', verbose_name='WebAuthn 2FA enabled'),
+ field=models.BooleanField(
+ default=False,
+ help_text='check to enable WebAuthn-based two-factor authentication',
+ verbose_name='WebAuthn 2FA enabled',
+ ),
),
migrations.CreateModel(
name='WebAuthnCredential',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
('name', models.CharField(max_length=100, verbose_name='device name')),
- ('cred_id', models.CharField(max_length=255, unique=True, verbose_name='credential ID')),
+ (
+ 'cred_id',
+ models.CharField(
+ max_length=255, unique=True, verbose_name='credential ID'
+ ),
+ ),
('public_key', models.TextField(verbose_name='public key')),
('counter', models.BigIntegerField(verbose_name='sign counter')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='webauthn_credentials', to='judge.Profile', verbose_name='user')),
+ (
+ 'user',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='webauthn_credentials',
+ to='judge.Profile',
+ verbose_name='user',
+ ),
+ ),
],
),
]
diff --git a/judge/migrations/0106_user_data_download.py b/judge/migrations/0106_user_data_download.py
index 1a13e431bb..acd0ae416c 100644
--- a/judge/migrations/0106_user_data_download.py
+++ b/judge/migrations/0106_user_data_download.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0105_webauthn'),
]
@@ -13,6 +12,8 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='profile',
name='data_last_downloaded',
- field=models.DateTimeField(blank=True, null=True, verbose_name='last data download time'),
+ field=models.DateTimeField(
+ blank=True, null=True, verbose_name='last data download time'
+ ),
),
]
diff --git a/judge/migrations/0107_submission_lock.py b/judge/migrations/0107_submission_lock.py
index 1d5f796b89..48df7fd1e6 100644
--- a/judge/migrations/0107_submission_lock.py
+++ b/judge/migrations/0107_submission_lock.py
@@ -9,11 +9,12 @@ def updatecontestsubmissions(apps, schema_editor):
Contest.objects.filter(end_time__lt=timezone.now()).update(is_locked=True)
Submission = apps.get_model('judge', 'Submission')
- Submission.objects.filter(contest_object__is_locked=True, contest__participation__virtual=0).update(is_locked=True)
+ Submission.objects.filter(
+ contest_object__is_locked=True, contest__participation__virtual=0
+ ).update(is_locked=True)
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0106_user_data_download'),
]
@@ -21,21 +22,55 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='contest',
- options={'permissions': (('see_private_contest', 'See private contests'), ('edit_own_contest', 'Edit own contests'), ('edit_all_contest', 'Edit all contests'), ('clone_contest', 'Clone contest'), ('moss_contest', 'MOSS contest'), ('contest_rating', 'Rate contests'), ('contest_access_code', 'Contest access codes'), ('create_private_contest', 'Create private contests'), ('change_contest_visibility', 'Change contest visibility'), ('contest_problem_label', 'Edit contest problem label script'), ('lock_contest', 'Change lock status of contest')), 'verbose_name': 'contest', 'verbose_name_plural': 'contests'},
+ options={
+ 'permissions': (
+ ('see_private_contest', 'See private contests'),
+ ('edit_own_contest', 'Edit own contests'),
+ ('edit_all_contest', 'Edit all contests'),
+ ('clone_contest', 'Clone contest'),
+ ('moss_contest', 'MOSS contest'),
+ ('contest_rating', 'Rate contests'),
+ ('contest_access_code', 'Contest access codes'),
+ ('create_private_contest', 'Create private contests'),
+ ('change_contest_visibility', 'Change contest visibility'),
+ ('contest_problem_label', 'Edit contest problem label script'),
+ ('lock_contest', 'Change lock status of contest'),
+ ),
+ 'verbose_name': 'contest',
+ 'verbose_name_plural': 'contests',
+ },
),
migrations.AlterModelOptions(
name='submission',
- options={'permissions': (('abort_any_submission', 'Abort any submission'), ('rejudge_submission', 'Rejudge the submission'), ('rejudge_submission_lot', 'Rejudge a lot of submissions'), ('spam_submission', 'Submit without limit'), ('view_all_submission', 'View all submission'), ('resubmit_other', "Resubmit others' submission"), ('lock_submission', 'Change lock status of submission')), 'verbose_name': 'submission', 'verbose_name_plural': 'submissions'},
+ options={
+ 'permissions': (
+ ('abort_any_submission', 'Abort any submission'),
+ ('rejudge_submission', 'Rejudge the submission'),
+ ('rejudge_submission_lot', 'Rejudge a lot of submissions'),
+ ('spam_submission', 'Submit without limit'),
+ ('view_all_submission', 'View all submission'),
+ ('resubmit_other', "Resubmit others' submission"),
+ ('lock_submission', 'Change lock status of submission'),
+ ),
+ 'verbose_name': 'submission',
+ 'verbose_name_plural': 'submissions',
+ },
),
migrations.AddField(
model_name='contest',
name='is_locked',
- field=models.BooleanField(default=False, help_text='Prevent submissions from this contest from being rejudged.', verbose_name='contest lock'),
+ field=models.BooleanField(
+ default=False,
+ help_text='Prevent submissions from this contest from being rejudged.',
+ verbose_name='contest lock',
+ ),
),
migrations.AddField(
model_name='submission',
name='is_locked',
field=models.BooleanField(default=False, verbose_name='lock submission'),
),
- migrations.RunPython(updatecontestsubmissions, reverse_code=migrations.RunPython.noop),
+ migrations.RunPython(
+ updatecontestsubmissions, reverse_code=migrations.RunPython.noop
+ ),
]
diff --git a/judge/migrations/0108_bleach_problems.py b/judge/migrations/0108_bleach_problems.py
index 7e0e2ce61b..3f32aa44c1 100644
--- a/judge/migrations/0108_bleach_problems.py
+++ b/judge/migrations/0108_bleach_problems.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0107_submission_lock'),
]
@@ -12,11 +11,27 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='problem',
- options={'permissions': (('see_private_problem', 'See hidden problems'), ('edit_own_problem', 'Edit own problems'), ('edit_all_problem', 'Edit all problems'), ('edit_public_problem', 'Edit all public problems'), ('problem_full_markup', 'Edit problems with full markup'), ('clone_problem', 'Clone problem'), ('change_public_visibility', 'Change is_public field'), ('change_manually_managed', 'Change is_manually_managed field'), ('see_organization_problem', 'See organization-private problems')), 'verbose_name': 'problem', 'verbose_name_plural': 'problems'},
+ options={
+ 'permissions': (
+ ('see_private_problem', 'See hidden problems'),
+ ('edit_own_problem', 'Edit own problems'),
+ ('edit_all_problem', 'Edit all problems'),
+ ('edit_public_problem', 'Edit all public problems'),
+ ('problem_full_markup', 'Edit problems with full markup'),
+ ('clone_problem', 'Clone problem'),
+ ('change_public_visibility', 'Change is_public field'),
+ ('change_manually_managed', 'Change is_manually_managed field'),
+ ('see_organization_problem', 'See organization-private problems'),
+ ),
+ 'verbose_name': 'problem',
+ 'verbose_name_plural': 'problems',
+ },
),
migrations.AddField(
model_name='problem',
name='is_full_markup',
- field=models.BooleanField(default=False, verbose_name='allow full markdown access'),
+ field=models.BooleanField(
+ default=False, verbose_name='allow full markdown access'
+ ),
),
]
diff --git a/judge/migrations/0109_scratch_codes.py b/judge/migrations/0109_scratch_codes.py
index 4c44d9e446..70ab158fb9 100644
--- a/judge/migrations/0109_scratch_codes.py
+++ b/judge/migrations/0109_scratch_codes.py
@@ -8,7 +8,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0108_bleach_problems'),
]
@@ -17,6 +16,18 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='profile',
name='scratch_codes',
- field=judge.models.profile.EncryptedNullCharField(blank=True, help_text='JSON array of 16 character base32-encoded codes for scratch codes', max_length=255, null=True, validators=[django.core.validators.RegexValidator(r'^(\[\])?$|^\[("[A-Z0-9]{16}", *)*"[A-Z0-9]{16}"\]$', 'Scratch codes must be empty or a JSON array of 16-character base32 codes')], verbose_name='scratch codes'),
+ field=judge.models.profile.EncryptedNullCharField(
+ blank=True,
+ help_text='JSON array of 16 character base32-encoded codes for scratch codes',
+ max_length=255,
+ null=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ r'^(\[\])?$|^\[("[A-Z0-9]{16}", *)*"[A-Z0-9]{16}"\]$',
+ 'Scratch codes must be empty or a JSON array of 16-character base32 codes',
+ )
+ ],
+ verbose_name='scratch codes',
+ ),
),
]
diff --git a/judge/migrations/0110_default_output_prefix_override.py b/judge/migrations/0110_default_output_prefix_override.py
index 0172b33015..a6c15812d4 100644
--- a/judge/migrations/0110_default_output_prefix_override.py
+++ b/judge/migrations/0110_default_output_prefix_override.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0109_scratch_codes'),
]
@@ -13,6 +12,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contestproblem',
name='output_prefix_override',
- field=models.IntegerField(blank=True, default=0, null=True, verbose_name='output prefix length override'),
+ field=models.IntegerField(
+ blank=True,
+ default=0,
+ null=True,
+ verbose_name='output prefix length override',
+ ),
),
]
diff --git a/judge/migrations/0111_blank_assignees_ticket.py b/judge/migrations/0111_blank_assignees_ticket.py
index 028f5d9e88..b45186b20b 100644
--- a/judge/migrations/0111_blank_assignees_ticket.py
+++ b/judge/migrations/0111_blank_assignees_ticket.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0110_default_output_prefix_override'),
]
@@ -13,6 +12,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='ticket',
name='assignees',
- field=models.ManyToManyField(blank=True, related_name='assigned_tickets', to='judge.Profile', verbose_name='assignees'),
+ field=models.ManyToManyField(
+ blank=True,
+ related_name='assigned_tickets',
+ to='judge.Profile',
+ verbose_name='assignees',
+ ),
),
]
diff --git a/judge/migrations/0112_language_extensions.py b/judge/migrations/0112_language_extensions.py
index ad8d3b1778..85519454fb 100644
--- a/judge/migrations/0112_language_extensions.py
+++ b/judge/migrations/0112_language_extensions.py
@@ -73,7 +73,10 @@ def update_language_extensions(apps, schema_editor):
try:
extension = extension_mapping[language.key]
except KeyError:
- print('Warning: no extension found for %s. Setting extension to language key.' % language.key)
+ print(
+ 'Warning: no extension found for %s. Setting extension to language key.'
+ % language.key
+ )
extension = language.key.lower()
language.extension = extension
@@ -82,11 +85,12 @@ def update_language_extensions(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0111_blank_assignees_ticket'),
]
operations = [
- migrations.RunPython(update_language_extensions, reverse_code=migrations.RunPython.noop),
+ migrations.RunPython(
+ update_language_extensions, reverse_code=migrations.RunPython.noop
+ ),
]
diff --git a/judge/migrations/0113_contest_decimal_points.py b/judge/migrations/0113_contest_decimal_points.py
index 9031157dd6..911f0bf720 100644
--- a/judge/migrations/0113_contest_decimal_points.py
+++ b/judge/migrations/0113_contest_decimal_points.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0112_language_extensions'),
]
@@ -13,7 +12,15 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='points_precision',
- field=models.IntegerField(default=3, help_text='Number of digits to round points to.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10)], verbose_name='precision points'),
+ field=models.IntegerField(
+ default=3,
+ help_text='Number of digits to round points to.',
+ validators=[
+ django.core.validators.MinValueValidator(0),
+ django.core.validators.MaxValueValidator(10),
+ ],
+ verbose_name='precision points',
+ ),
),
migrations.AlterField(
model_name='contestparticipation',
diff --git a/judge/migrations/0114_remove_org_registrant.py b/judge/migrations/0114_remove_org_registrant.py
index e494942d43..86e5574bd1 100644
--- a/judge/migrations/0114_remove_org_registrant.py
+++ b/judge/migrations/0114_remove_org_registrant.py
@@ -12,7 +12,6 @@ def make_admin_registrant(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0113_contest_decimal_points'),
]
diff --git a/judge/migrations/0115_contest_scoreboard_visibility.py b/judge/migrations/0115_contest_scoreboard_visibility.py
index fb3582a136..69f1e3c2b4 100644
--- a/judge/migrations/0115_contest_scoreboard_visibility.py
+++ b/judge/migrations/0115_contest_scoreboard_visibility.py
@@ -10,11 +10,12 @@ def hide_scoreboard_eq_true(apps, schema_editor):
def scoreboard_visibility_eq_contest(apps, schema_editor):
Contest = apps.get_model('judge', 'Contest')
- Contest.objects.filter(scoreboard_visibility__in=('C', 'P')).update(hide_scoreboard=True)
+ Contest.objects.filter(scoreboard_visibility__in=('C', 'P')).update(
+ hide_scoreboard=True
+ )
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0114_remove_org_registrant'),
]
@@ -23,9 +24,21 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='scoreboard_visibility',
- field=models.CharField(choices=[('V', 'Visible'), ('C', 'Hidden for duration of contest'), ('P', 'Hidden for duration of participation')], default='V', help_text='Scoreboard visibility through the duration of the contest', max_length=1, verbose_name='scoreboard visibility'),
+ field=models.CharField(
+ choices=[
+ ('V', 'Visible'),
+ ('C', 'Hidden for duration of contest'),
+ ('P', 'Hidden for duration of participation'),
+ ],
+ default='V',
+ help_text='Scoreboard visibility through the duration of the contest',
+ max_length=1,
+ verbose_name='scoreboard visibility',
+ ),
+ ),
+ migrations.RunPython(
+ hide_scoreboard_eq_true, scoreboard_visibility_eq_contest, atomic=True
),
- migrations.RunPython(hide_scoreboard_eq_true, scoreboard_visibility_eq_contest, atomic=True),
migrations.RemoveField(
model_name='contest',
name='hide_scoreboard',
diff --git a/judge/migrations/0116_contest_curator_and_tester.py b/judge/migrations/0116_contest_curator_and_tester.py
index 389e7dafee..fe61195f04 100644
--- a/judge/migrations/0116_contest_curator_and_tester.py
+++ b/judge/migrations/0116_contest_curator_and_tester.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0115_contest_scoreboard_visibility'),
]
@@ -13,7 +12,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contest',
name='organizers',
- field=models.ManyToManyField(help_text='These users will be able to edit the contest.', related_name='_contest_authors_+', to='judge.Profile'),
+ field=models.ManyToManyField(
+ help_text='These users will be able to edit the contest.',
+ related_name='_contest_authors_+',
+ to='judge.Profile',
+ ),
),
migrations.RenameField(
model_name='contest',
@@ -23,11 +26,21 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='curators',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to edit the contest, but will not be listed as authors.', related_name='_contest_curators_+', to='judge.Profile'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to edit the contest, but will not be listed as authors.',
+ related_name='_contest_curators_+',
+ to='judge.Profile',
+ ),
),
migrations.AddField(
model_name='contest',
name='testers',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to view the contest, but not edit it.', related_name='_contest_testers_+', to='judge.Profile'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to view the contest, but not edit it.',
+ related_name='_contest_testers_+',
+ to='judge.Profile',
+ ),
),
]
diff --git a/judge/migrations/0117_remove_private_messages.py b/judge/migrations/0117_remove_private_messages.py
index 67456e40f3..ced88ba0cf 100644
--- a/judge/migrations/0117_remove_private_messages.py
+++ b/judge/migrations/0117_remove_private_messages.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0116_contest_curator_and_tester'),
]
diff --git a/judge/migrations/0118_convert_to_dates.py b/judge/migrations/0118_convert_to_dates.py
index 94bdc3d92e..ef2350cfe4 100644
--- a/judge/migrations/0118_convert_to_dates.py
+++ b/judge/migrations/0118_convert_to_dates.py
@@ -26,7 +26,6 @@ def convert_to_boolean(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0117_remove_private_messages'),
]
@@ -35,17 +34,26 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='locked_after',
- field=models.DateTimeField(blank=True, help_text='Prevent submissions from this contest from being rejudged after this date.', null=True, verbose_name='contest lock'),
+ field=models.DateTimeField(
+ blank=True,
+ help_text='Prevent submissions from this contest from being rejudged after this date.',
+ null=True,
+ verbose_name='contest lock',
+ ),
),
migrations.AddField(
model_name='submission',
name='locked_after',
- field=models.DateTimeField(blank=True, null=True, verbose_name='submission lock'),
+ field=models.DateTimeField(
+ blank=True, null=True, verbose_name='submission lock'
+ ),
),
migrations.AddField(
model_name='submission',
name='rejudged_date',
- field=models.DateTimeField(blank=True, null=True, verbose_name='last rejudge date by admin'),
+ field=models.DateTimeField(
+ blank=True, null=True, verbose_name='last rejudge date by admin'
+ ),
),
migrations.RunPython(convert_to_datetime, convert_to_boolean, atomic=True),
migrations.RemoveField(
diff --git a/judge/migrations/0119_hide_problem_authors.py b/judge/migrations/0119_hide_problem_authors.py
index 31c2808040..362f37fbaf 100644
--- a/judge/migrations/0119_hide_problem_authors.py
+++ b/judge/migrations/0119_hide_problem_authors.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0118_convert_to_dates'),
]
@@ -13,6 +12,10 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='hide_problem_authors',
- field=models.BooleanField(default=False, help_text='Whether problem authors should be hidden by default.', verbose_name='hide problem authors'),
+ field=models.BooleanField(
+ default=False,
+ help_text='Whether problem authors should be hidden by default.',
+ verbose_name='hide problem authors',
+ ),
),
]
diff --git a/judge/migrations/0120_totp_no_reuse.py b/judge/migrations/0120_totp_no_reuse.py
index df2d4ba028..d420876a40 100644
--- a/judge/migrations/0120_totp_no_reuse.py
+++ b/judge/migrations/0120_totp_no_reuse.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0119_hide_problem_authors'),
]
diff --git a/judge/migrations/0121_per_problem_sub_access_control.py b/judge/migrations/0121_per_problem_sub_access_control.py
index d57e6850fc..0c312e49e6 100644
--- a/judge/migrations/0121_per_problem_sub_access_control.py
+++ b/judge/migrations/0121_per_problem_sub_access_control.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0120_totp_no_reuse'),
]
@@ -13,6 +12,16 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='problem',
name='submission_source_visibility_mode',
- field=models.CharField(choices=[('F', 'Follow global setting'), ('A', 'Always visible'), ('S', 'Visible if problem solved'), ('O', 'Only own submissions')], default='F', max_length=1, verbose_name='submission source visibility'),
+ field=models.CharField(
+ choices=[
+ ('F', 'Follow global setting'),
+ ('A', 'Always visible'),
+ ('S', 'Visible if problem solved'),
+ ('O', 'Only own submissions'),
+ ],
+ default='F',
+ max_length=1,
+ verbose_name='submission source visibility',
+ ),
),
]
diff --git a/judge/migrations/0122_username_display_override.py b/judge/migrations/0122_username_display_override.py
index c8650a4cfa..db57865262 100644
--- a/judge/migrations/0122_username_display_override.py
+++ b/judge/migrations/0122_username_display_override.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0121_per_problem_sub_access_control'),
]
@@ -13,6 +12,11 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='profile',
name='username_display_override',
- field=models.CharField(blank=True, max_length=100, verbose_name='display name override', help_text='name displayed in place of username'),
+ field=models.CharField(
+ blank=True,
+ max_length=100,
+ verbose_name='display name override',
+ help_text='name displayed in place of username',
+ ),
),
]
diff --git a/judge/migrations/0123_contest_rating_elo_mmr.py b/judge/migrations/0123_contest_rating_elo_mmr.py
index e5957e63f1..855f900c1f 100644
--- a/judge/migrations/0123_contest_rating_elo_mmr.py
+++ b/judge/migrations/0123_contest_rating_elo_mmr.py
@@ -53,7 +53,9 @@ def WP(RA, RB, VA, VB):
return (math.erf((RB - RA) / math.sqrt(2 * (VA * VA + VB * VB))) + 1) / 2.0
-def recalculate_ratings(old_rating, old_volatility, actual_rank, times_rated, is_disqualified):
+def recalculate_ratings(
+ old_rating, old_volatility, actual_rank, times_rated, is_disqualified
+):
# actual_rank: 1 is first place, N is last place
# if there are ties, use the average of places (if places 2, 3, 4, 5 tie, use 3.5 for all of them)
@@ -74,7 +76,9 @@ def recalculate_ratings(old_rating, old_volatility, actual_rank, times_rated, is
for i in range(N):
ERank = 0.5
for j in range(N):
- ERank += WP(old_rating[i], old_rating[j], old_volatility[i], old_volatility[j])
+ ERank += WP(
+ old_rating[i], old_rating[j], old_volatility[i], old_volatility[j]
+ )
EPerf = -normal_CDF_inverse((ERank - 0.5) / N)
APerf = -normal_CDF_inverse((actual_rank[i] - 0.5) / N)
@@ -98,8 +102,10 @@ def recalculate_ratings(old_rating, old_volatility, actual_rank, times_rated, is
if times_rated[i] == 0:
new_volatility[i] = 385
else:
- new_volatility[i] = math.sqrt(((new_rating[i] - old_rating[i]) ** 2) / Weight +
- (old_volatility[i] ** 2) / (Weight + 1))
+ new_volatility[i] = math.sqrt(
+ ((new_rating[i] - old_rating[i]) ** 2) / Weight
+ + (old_volatility[i] ** 2) / (Weight + 1)
+ )
if is_disqualified[i]:
# DQed users can manipulate TopCoder ratings to get higher volatility in order to increase their rating
@@ -112,23 +118,49 @@ def recalculate_ratings(old_rating, old_volatility, actual_rank, times_rated, is
# inflate a little if we have to so people who placed first don't lose rating
best_rank = min(actual_rank)
for i in range(N):
- if abs(actual_rank[i] - best_rank) <= 1e-3 and new_rating[i] < old_rating[i] + 1:
+ if (
+ abs(actual_rank[i] - best_rank) <= 1e-3
+ and new_rating[i] < old_rating[i] + 1
+ ):
new_rating[i] = old_rating[i] + 1
- return list(map(int, map(round, new_rating))), list(map(int, map(round, new_volatility)))
+ return list(map(int, map(round, new_rating))), list(
+ map(int, map(round, new_volatility))
+ )
def tc_rate_contest(contest, Rating, Profile):
rating_subquery = Rating.objects.filter(user=OuterRef('user'))
rating_sorted = rating_subquery.order_by('-contest__end_time')
- users = contest.users.order_by('is_disqualified', '-score', 'cumtime', 'tiebreaker') \
- .annotate(submissions=Count('submission'),
- last_rating=Coalesce(Subquery(rating_sorted.values('rating')[:1]), 1200),
- volatility=Coalesce(Subquery(rating_sorted.values('volatility')[:1]), 535),
- times=Coalesce(Subquery(rating_subquery.order_by().values('user_id')
- .annotate(count=Count('id')).values('count')), 0)) \
- .exclude(user_id__in=contest.rate_exclude.all()) \
- .filter(virtual=0).values('id', 'user_id', 'score', 'cumtime', 'tiebreaker', 'is_disqualified',
- 'last_rating', 'volatility', 'times')
+ users = (
+ contest.users.order_by('is_disqualified', '-score', 'cumtime', 'tiebreaker')
+ .annotate(
+ submissions=Count('submission'),
+ last_rating=Coalesce(Subquery(rating_sorted.values('rating')[:1]), 1200),
+ volatility=Coalesce(Subquery(rating_sorted.values('volatility')[:1]), 535),
+ times=Coalesce(
+ Subquery(
+ rating_subquery.order_by()
+ .values('user_id')
+ .annotate(count=Count('id'))
+ .values('count')
+ ),
+ 0,
+ ),
+ )
+ .exclude(user_id__in=contest.rate_exclude.all())
+ .filter(virtual=0)
+ .values(
+ 'id',
+ 'user_id',
+ 'score',
+ 'cumtime',
+ 'tiebreaker',
+ 'is_disqualified',
+ 'last_rating',
+ 'volatility',
+ 'times',
+ )
+ )
if not contest.rate_all:
users = users.filter(submissions__gt=0)
if contest.rating_floor is not None:
@@ -144,17 +176,37 @@ def tc_rate_contest(contest, Rating, Profile):
old_rating = list(map(itemgetter('last_rating'), users))
old_volatility = list(map(itemgetter('volatility'), users))
times_ranked = list(map(itemgetter('times'), users))
- rating, volatility = recalculate_ratings(old_rating, old_volatility, ranking, times_ranked, is_disqualified)
+ rating, volatility = recalculate_ratings(
+ old_rating, old_volatility, ranking, times_ranked, is_disqualified
+ )
now = timezone.now()
- ratings = [Rating(user_id=i, contest=contest, rating=r, volatility=v, last_rated=now, participation_id=p, rank=z)
- for i, p, r, v, z in zip(user_ids, participation_ids, rating, volatility, ranking)]
+ ratings = [
+ Rating(
+ user_id=i,
+ contest=contest,
+ rating=r,
+ volatility=v,
+ last_rated=now,
+ participation_id=p,
+ rank=z,
+ )
+ for i, p, r, v, z in zip(
+ user_ids, participation_ids, rating, volatility, ranking
+ )
+ ]
Rating.objects.bulk_create(ratings)
- Profile.objects.filter(contest_history__contest=contest, contest_history__virtual=0).update(
- rating=Subquery(Rating.objects.filter(user=OuterRef('id'))
- .order_by('-contest__end_time').values('rating')[:1]))
+ Profile.objects.filter(
+ contest_history__contest=contest, contest_history__virtual=0
+ ).update(
+ rating=Subquery(
+ Rating.objects.filter(user=OuterRef('id'))
+ .order_by('-contest__end_time')
+ .values('rating')[:1]
+ )
+ )
# inspired by rate_all_view
@@ -166,7 +218,9 @@ def rate_tc(apps, schema_editor):
with schema_editor.connection.cursor() as cursor:
cursor.execute('TRUNCATE TABLE `%s`' % Rating._meta.db_table)
Profile.objects.update(rating=None)
- for contest in Contest.objects.filter(is_rated=True, end_time__lte=timezone.now()).order_by('end_time'):
+ for contest in Contest.objects.filter(
+ is_rated=True, end_time__lte=timezone.now()
+ ).order_by('end_time'):
tc_rate_contest(contest, Rating, Profile)
@@ -182,7 +236,6 @@ def rate_elo_mmr(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0122_username_display_override'),
]
diff --git a/judge/migrations/0124_contest_show_short_display.py b/judge/migrations/0124_contest_show_short_display.py
index ac805fee38..e083b843e0 100644
--- a/judge/migrations/0124_contest_show_short_display.py
+++ b/judge/migrations/0124_contest_show_short_display.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0123_contest_rating_elo_mmr'),
]
@@ -13,6 +12,10 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='show_short_display',
- field=models.BooleanField(default=False, help_text='Whether to show a section containing contest settings on the contest page or not.', verbose_name='show short form settings display'),
+ field=models.BooleanField(
+ default=False,
+ help_text='Whether to show a section containing contest settings on the contest page or not.',
+ verbose_name='show short form settings display',
+ ),
),
]
diff --git a/judge/migrations/0125_organization_classes.py b/judge/migrations/0125_organization_classes.py
index 66bcc3692d..f18e908f5c 100644
--- a/judge/migrations/0125_organization_classes.py
+++ b/judge/migrations/0125_organization_classes.py
@@ -5,7 +5,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0124_contest_show_short_display'),
]
@@ -14,20 +13,86 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='organization',
name='class_required',
- field=models.BooleanField(default=False, help_text='whether members are compelled to select a class when joining', verbose_name='class membership required'),
+ field=models.BooleanField(
+ default=False,
+ help_text='whether members are compelled to select a class when joining',
+ verbose_name='class membership required',
+ ),
),
migrations.CreateModel(
name='Class',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=128, unique=True, verbose_name='class name')),
- ('slug', models.SlugField(help_text='class name shown in URLs', max_length=128, verbose_name='class slug')),
- ('description', models.TextField(blank=True, verbose_name='class description')),
- ('is_active', models.BooleanField(default=True, verbose_name='is class active')),
- ('access_code', models.CharField(blank=True, help_text='student access code', max_length=7, null=True, verbose_name='access code')),
- ('admins', models.ManyToManyField(help_text='those who can approve membership to this class', related_name='class_admin_of', to='judge.Profile', verbose_name='administrators')),
- ('members', models.ManyToManyField(blank=True, related_name='classes', related_query_name='class', to='judge.Profile', verbose_name='members')),
- ('organization', models.ForeignKey(help_text='the organization that this class belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='classes', related_query_name='class', to='judge.Organization', verbose_name='organization')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'name',
+ models.CharField(
+ max_length=128, unique=True, verbose_name='class name'
+ ),
+ ),
+ (
+ 'slug',
+ models.SlugField(
+ help_text='class name shown in URLs',
+ max_length=128,
+ verbose_name='class slug',
+ ),
+ ),
+ (
+ 'description',
+ models.TextField(blank=True, verbose_name='class description'),
+ ),
+ (
+ 'is_active',
+ models.BooleanField(default=True, verbose_name='is class active'),
+ ),
+ (
+ 'access_code',
+ models.CharField(
+ blank=True,
+ help_text='student access code',
+ max_length=7,
+ null=True,
+ verbose_name='access code',
+ ),
+ ),
+ (
+ 'admins',
+ models.ManyToManyField(
+ help_text='those who can approve membership to this class',
+ related_name='class_admin_of',
+ to='judge.Profile',
+ verbose_name='administrators',
+ ),
+ ),
+ (
+ 'members',
+ models.ManyToManyField(
+ blank=True,
+ related_name='classes',
+ related_query_name='class',
+ to='judge.Profile',
+ verbose_name='members',
+ ),
+ ),
+ (
+ 'organization',
+ models.ForeignKey(
+ help_text='the organization that this class belongs to',
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='classes',
+ related_query_name='class',
+ to='judge.Organization',
+ verbose_name='organization',
+ ),
+ ),
],
options={
'verbose_name': 'class',
@@ -38,15 +103,30 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='classes',
- field=models.ManyToManyField(blank=True, help_text='If organization private, only these classes may see the contest', to='judge.Class', verbose_name='classes'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='If organization private, only these classes may see the contest',
+ to='judge.Class',
+ verbose_name='classes',
+ ),
),
migrations.AddConstraint(
model_name='class',
- constraint=models.UniqueConstraint(condition=models.Q(is_active=True), fields=('name',), name='unique_active_name'),
+ constraint=models.UniqueConstraint(
+ condition=models.Q(is_active=True),
+ fields=('name',),
+ name='unique_active_name',
+ ),
),
migrations.AddField(
model_name='organizationrequest',
name='request_class',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='judge.Class', verbose_name='class'),
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.Class',
+ verbose_name='class',
+ ),
),
]
diff --git a/judge/migrations/0126_infer_private_bools.py b/judge/migrations/0126_infer_private_bools.py
index 163dfea154..154e2da512 100644
--- a/judge/migrations/0126_infer_private_bools.py
+++ b/judge/migrations/0126_infer_private_bools.py
@@ -29,7 +29,6 @@ def sync_private_booleans(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0125_organization_classes'),
]
diff --git a/judge/migrations/0127_tester_see_scoreboard.py b/judge/migrations/0127_tester_see_scoreboard.py
index 9346fa9db7..ba293be69e 100644
--- a/judge/migrations/0127_tester_see_scoreboard.py
+++ b/judge/migrations/0127_tester_see_scoreboard.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0126_infer_private_bools'),
]
@@ -13,6 +12,10 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='tester_see_scoreboard',
- field=models.BooleanField(default=False, help_text='If testers can see the scoreboard.', verbose_name='testers see scoreboard'),
+ field=models.BooleanField(
+ default=False,
+ help_text='If testers can see the scoreboard.',
+ verbose_name='testers see scoreboard',
+ ),
),
]
diff --git a/judge/migrations/0128_limit_join_organizations.py b/judge/migrations/0128_limit_join_organizations.py
index d346802169..79add3b0cb 100644
--- a/judge/migrations/0128_limit_join_organizations.py
+++ b/judge/migrations/0128_limit_join_organizations.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0127_tester_see_scoreboard'),
]
@@ -13,11 +12,19 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='join_organizations',
- field=models.ManyToManyField(blank=True, help_text='If non-empty, only these organizations may join the contest', related_name='join_only_contests', to='judge.Organization', verbose_name='join organizations'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='If non-empty, only these organizations may join the contest',
+ related_name='join_only_contests',
+ to='judge.Organization',
+ verbose_name='join organizations',
+ ),
),
migrations.AddField(
model_name='contest',
name='limit_join_organizations',
- field=models.BooleanField(default=False, verbose_name='limit organizations that can join'),
+ field=models.BooleanField(
+ default=False, verbose_name='limit organizations that can join'
+ ),
),
]
diff --git a/judge/migrations/0129_see_scoreboard_subs.py b/judge/migrations/0129_see_scoreboard_subs.py
index 4418f3d021..222b5c3e24 100644
--- a/judge/migrations/0129_see_scoreboard_subs.py
+++ b/judge/migrations/0129_see_scoreboard_subs.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0128_limit_join_organizations'),
]
@@ -13,11 +12,21 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='tester_see_submissions',
- field=models.BooleanField(default=False, help_text='If testers can see in-contest submissions.', verbose_name='testers see submissions'),
+ field=models.BooleanField(
+ default=False,
+ help_text='If testers can see in-contest submissions.',
+ verbose_name='testers see submissions',
+ ),
),
migrations.AddField(
model_name='contest',
name='view_contest_submissions',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to see in-contest submissions.', related_name='view_contest_submissions', to='judge.Profile', verbose_name='can see contest submissions'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to see in-contest submissions.',
+ related_name='view_contest_submissions',
+ to='judge.Profile',
+ verbose_name='can see contest submissions',
+ ),
),
]
diff --git a/judge/migrations/0130_blogpost_change_visibility.py b/judge/migrations/0130_blogpost_change_visibility.py
index a90123c429..05136d1275 100644
--- a/judge/migrations/0130_blogpost_change_visibility.py
+++ b/judge/migrations/0130_blogpost_change_visibility.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0129_see_scoreboard_subs'),
]
@@ -12,6 +11,13 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='blogpost',
- options={'permissions': (('edit_all_post', 'Edit all posts'), ('change_post_visibility', 'Edit post visibility')), 'verbose_name': 'blog post', 'verbose_name_plural': 'blog posts'},
+ options={
+ 'permissions': (
+ ('edit_all_post', 'Edit all posts'),
+ ('change_post_visibility', 'Edit post visibility'),
+ ),
+ 'verbose_name': 'blog post',
+ 'verbose_name_plural': 'blog posts',
+ },
),
]
diff --git a/judge/migrations/0131_spectate_contests.py b/judge/migrations/0131_spectate_contests.py
index 6cfc95354b..3e3bdeda58 100644
--- a/judge/migrations/0131_spectate_contests.py
+++ b/judge/migrations/0131_spectate_contests.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0130_blogpost_change_visibility'),
]
@@ -13,21 +12,40 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='contest',
name='spectators',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to spectate the contest, but not see the problems ahead of time.', related_name='spectated_contests', to='judge.Profile'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to spectate the contest, but not see the problems ahead of time.',
+ related_name='spectated_contests',
+ to='judge.Profile',
+ ),
),
migrations.AlterField(
model_name='contest',
name='authors',
- field=models.ManyToManyField(help_text='These users will be able to edit the contest.', related_name='authored_contests', to='judge.Profile'),
+ field=models.ManyToManyField(
+ help_text='These users will be able to edit the contest.',
+ related_name='authored_contests',
+ to='judge.Profile',
+ ),
),
migrations.AlterField(
model_name='contest',
name='curators',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to edit the contest, but will not be listed as authors.', related_name='curated_contests', to='judge.Profile'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to edit the contest, but will not be listed as authors.',
+ related_name='curated_contests',
+ to='judge.Profile',
+ ),
),
migrations.AlterField(
model_name='contest',
name='testers',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to view the contest, but not edit it.', related_name='tested_contests', to='judge.Profile'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to view the contest, but not edit it.',
+ related_name='tested_contests',
+ to='judge.Profile',
+ ),
),
]
diff --git a/judge/migrations/0132_no_self_vote.py b/judge/migrations/0132_no_self_vote.py
index 078d63a10b..6239dbd9a7 100644
--- a/judge/migrations/0132_no_self_vote.py
+++ b/judge/migrations/0132_no_self_vote.py
@@ -9,13 +9,14 @@ def delete_self_votes(apps, schema_editor):
CommentVote.objects.filter(voter=F('comment__author')).delete()
- votes = CommentVote.objects.filter(comment=OuterRef('id')).order_by().values('comment')
+ votes = (
+ CommentVote.objects.filter(comment=OuterRef('id')).order_by().values('comment')
+ )
total_votes = votes.annotate(total=Sum('score')).values('total')
Comment.objects.update(score=Coalesce(Subquery(total_votes), 0))
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0131_spectate_contests'),
]
diff --git a/judge/migrations/0133_add_problem_data_hints.py b/judge/migrations/0133_add_problem_data_hints.py
index 39d2ed0c3c..893a083860 100644
--- a/judge/migrations/0133_add_problem_data_hints.py
+++ b/judge/migrations/0133_add_problem_data_hints.py
@@ -10,7 +10,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0132_no_self_vote'),
]
@@ -18,19 +17,33 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
name='commentlock',
- options={'permissions': (('override_comment_lock', 'Override comment lock'),), 'verbose_name': 'comment lock', 'verbose_name_plural': 'comment locks'},
+ options={
+ 'permissions': (('override_comment_lock', 'Override comment lock'),),
+ 'verbose_name': 'comment lock',
+ 'verbose_name_plural': 'comment locks',
+ },
),
migrations.AlterModelOptions(
name='contestproblem',
- options={'ordering': ('order',), 'verbose_name': 'contest problem', 'verbose_name_plural': 'contest problems'},
+ options={
+ 'ordering': ('order',),
+ 'verbose_name': 'contest problem',
+ 'verbose_name_plural': 'contest problems',
+ },
),
migrations.AlterModelOptions(
name='problemclarification',
- options={'verbose_name': 'problem clarification', 'verbose_name_plural': 'problem clarifications'},
+ options={
+ 'verbose_name': 'problem clarification',
+ 'verbose_name_plural': 'problem clarifications',
+ },
),
migrations.AlterModelOptions(
name='submissionsource',
- options={'verbose_name': 'submission source', 'verbose_name_plural': 'submission sources'},
+ options={
+ 'verbose_name': 'submission source',
+ 'verbose_name_plural': 'submission sources',
+ },
),
migrations.AlterModelOptions(
name='ticket',
@@ -38,36 +51,66 @@ class Migration(migrations.Migration):
),
migrations.AlterModelOptions(
name='ticketmessage',
- options={'verbose_name': 'ticket message', 'verbose_name_plural': 'ticket messages'},
+ options={
+ 'verbose_name': 'ticket message',
+ 'verbose_name_plural': 'ticket messages',
+ },
),
migrations.AlterModelOptions(
name='webauthncredential',
- options={'verbose_name': 'WebAuthn credential', 'verbose_name_plural': 'WebAuthn credentials'},
+ options={
+ 'verbose_name': 'WebAuthn credential',
+ 'verbose_name_plural': 'WebAuthn credentials',
+ },
),
migrations.AlterField(
model_name='blogpost',
name='og_image',
- field=models.CharField(blank=True, default='', max_length=150, verbose_name='OpenGraph image'),
+ field=models.CharField(
+ blank=True, default='', max_length=150, verbose_name='OpenGraph image'
+ ),
),
migrations.AlterField(
model_name='class',
name='access_code',
- field=models.CharField(blank=True, help_text='Student access code.', max_length=7, null=True, verbose_name='access code'),
+ field=models.CharField(
+ blank=True,
+ help_text='Student access code.',
+ max_length=7,
+ null=True,
+ verbose_name='access code',
+ ),
),
migrations.AlterField(
model_name='class',
name='admins',
- field=models.ManyToManyField(help_text='Those who can approve membership to this class.', related_name='class_admin_of', to='judge.Profile', verbose_name='administrators'),
+ field=models.ManyToManyField(
+ help_text='Those who can approve membership to this class.',
+ related_name='class_admin_of',
+ to='judge.Profile',
+ verbose_name='administrators',
+ ),
),
migrations.AlterField(
model_name='class',
name='organization',
- field=models.ForeignKey(help_text='The organization that this class belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='classes', related_query_name='class', to='judge.organization', verbose_name='organization'),
+ field=models.ForeignKey(
+ help_text='The organization that this class belongs to.',
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='classes',
+ related_query_name='class',
+ to='judge.organization',
+ verbose_name='organization',
+ ),
),
migrations.AlterField(
model_name='class',
name='slug',
- field=models.SlugField(help_text='Class name shown in URLs.', max_length=128, verbose_name='class slug'),
+ field=models.SlugField(
+ help_text='Class name shown in URLs.',
+ max_length=128,
+ verbose_name='class slug',
+ ),
),
migrations.AlterField(
model_name='comment',
@@ -92,117 +135,254 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='contest',
name='authors',
- field=models.ManyToManyField(help_text='These users will be able to edit the contest.', related_name='authored_contests', to='judge.Profile', verbose_name='authors'),
+ field=models.ManyToManyField(
+ help_text='These users will be able to edit the contest.',
+ related_name='authored_contests',
+ to='judge.Profile',
+ verbose_name='authors',
+ ),
),
migrations.AlterField(
model_name='contest',
name='classes',
- field=models.ManyToManyField(blank=True, help_text='If organization private, only these classes may see the contest.', to='judge.Class', verbose_name='classes'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='If organization private, only these classes may see the contest.',
+ to='judge.Class',
+ verbose_name='classes',
+ ),
),
migrations.AlterField(
model_name='contest',
name='curators',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to edit the contest, but will not be listed as authors.', related_name='curated_contests', to='judge.Profile', verbose_name='curators'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to edit the contest, but will not be listed as authors.',
+ related_name='curated_contests',
+ to='judge.Profile',
+ verbose_name='curators',
+ ),
),
migrations.AlterField(
model_name='contest',
name='format_name',
- field=models.CharField(choices=[('atcoder', 'AtCoder'), ('default', 'Default'), ('ecoo', 'ECOO'), ('icpc', 'ICPC'), ('ioi', 'IOI (pre-2016)'), ('ioi16', 'IOI')], default='default', help_text='The contest format module to use.', max_length=32, verbose_name='contest format'),
+ field=models.CharField(
+ choices=[
+ ('atcoder', 'AtCoder'),
+ ('default', 'Default'),
+ ('ecoo', 'ECOO'),
+ ('icpc', 'ICPC'),
+ ('ioi', 'IOI (pre-2016)'),
+ ('ioi16', 'IOI'),
+ ],
+ default='default',
+ help_text='The contest format module to use.',
+ max_length=32,
+ verbose_name='contest format',
+ ),
),
migrations.AlterField(
model_name='contest',
name='join_organizations',
- field=models.ManyToManyField(blank=True, help_text='If non-empty, only these organizations may join the contest.', related_name='join_only_contests', to='judge.Organization', verbose_name='join organizations'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='If non-empty, only these organizations may join the contest.',
+ related_name='join_only_contests',
+ to='judge.Organization',
+ verbose_name='join organizations',
+ ),
),
migrations.AlterField(
model_name='contest',
name='logo_override_image',
- field=models.CharField(blank=True, default='', help_text='This image will replace the default site logo for users inside the contest.', max_length=150, verbose_name='logo override image'),
+ field=models.CharField(
+ blank=True,
+ default='',
+ help_text='This image will replace the default site logo for users inside the contest.',
+ max_length=150,
+ verbose_name='logo override image',
+ ),
),
migrations.AlterField(
model_name='contest',
name='organizations',
- field=models.ManyToManyField(blank=True, help_text='If non-empty, only these organizations may see the contest.', to='judge.Organization', verbose_name='organizations'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='If non-empty, only these organizations may see the contest.',
+ to='judge.Organization',
+ verbose_name='organizations',
+ ),
),
migrations.AlterField(
model_name='contest',
name='private_contestants',
- field=models.ManyToManyField(blank=True, help_text='If non-empty, only these users may see the contest.', related_name='_judge_contest_private_contestants_+', to='judge.Profile', verbose_name='private contestants'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='If non-empty, only these users may see the contest.',
+ related_name='_judge_contest_private_contestants_+',
+ to='judge.Profile',
+ verbose_name='private contestants',
+ ),
),
migrations.AlterField(
model_name='contest',
name='rating_ceiling',
- field=models.IntegerField(blank=True, help_text='Do not rate users who have a higher rating.', null=True, verbose_name='rating ceiling'),
+ field=models.IntegerField(
+ blank=True,
+ help_text='Do not rate users who have a higher rating.',
+ null=True,
+ verbose_name='rating ceiling',
+ ),
),
migrations.AlterField(
model_name='contest',
name='rating_floor',
- field=models.IntegerField(blank=True, help_text='Do not rate users who have a lower rating.', null=True, verbose_name='rating floor'),
+ field=models.IntegerField(
+ blank=True,
+ help_text='Do not rate users who have a lower rating.',
+ null=True,
+ verbose_name='rating floor',
+ ),
),
migrations.AlterField(
model_name='contest',
name='scoreboard_visibility',
- field=models.CharField(choices=[('V', 'Visible'), ('C', 'Hidden for duration of contest'), ('P', 'Hidden for duration of participation'), ('H', 'Hidden permanently')], default='V', help_text='Scoreboard visibility through the duration of the contest.', max_length=1, verbose_name='scoreboard visibility'),
+ field=models.CharField(
+ choices=[
+ ('V', 'Visible'),
+ ('C', 'Hidden for duration of contest'),
+ ('P', 'Hidden for duration of participation'),
+ ('H', 'Hidden permanently'),
+ ],
+ default='V',
+ help_text='Scoreboard visibility through the duration of the contest.',
+ max_length=1,
+ verbose_name='scoreboard visibility',
+ ),
),
migrations.AlterField(
model_name='contest',
name='spectators',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to spectate the contest, but not see the problems ahead of time.', related_name='spectated_contests', to='judge.Profile', verbose_name='spectators'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to spectate the contest, but not see the problems ahead of time.',
+ related_name='spectated_contests',
+ to='judge.Profile',
+ verbose_name='spectators',
+ ),
),
migrations.AlterField(
model_name='contest',
name='testers',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to view the contest, but not edit it.', related_name='tested_contests', to='judge.Profile', verbose_name='testers'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to view the contest, but not edit it.',
+ related_name='tested_contests',
+ to='judge.Profile',
+ verbose_name='testers',
+ ),
),
migrations.AlterField(
model_name='contestproblem',
name='max_submissions',
- field=models.IntegerField(blank=True, default=None, help_text='Maximum number of submissions for this problem, or leave blank for no limit.', null=True, validators=[judge.models.contest.MinValueOrNoneValidator(1, "Why include a problem you can't submit to?")], verbose_name='max submissions'),
+ field=models.IntegerField(
+ blank=True,
+ default=None,
+ help_text='Maximum number of submissions for this problem, or leave blank for no limit.',
+ null=True,
+ validators=[
+ judge.models.contest.MinValueOrNoneValidator(
+ 1, "Why include a problem you can't submit to?"
+ )
+ ],
+ verbose_name='max submissions',
+ ),
),
migrations.AlterField(
model_name='judge',
name='auth_key',
- field=models.CharField(help_text='A key to authenticate this judge.', max_length=100, verbose_name='authentication key'),
+ field=models.CharField(
+ help_text='A key to authenticate this judge.',
+ max_length=100,
+ verbose_name='authentication key',
+ ),
),
migrations.AlterField(
model_name='judge',
name='last_ip',
- field=models.GenericIPAddressField(blank=True, null=True, verbose_name='last connected IP'),
+ field=models.GenericIPAddressField(
+ blank=True, null=True, verbose_name='last connected IP'
+ ),
),
migrations.AlterField(
model_name='judge',
name='name',
- field=models.CharField(help_text='Server name, hostname-style.', max_length=50, unique=True, verbose_name='judge name'),
+ field=models.CharField(
+ help_text='Server name, hostname-style.',
+ max_length=50,
+ unique=True,
+ verbose_name='judge name',
+ ),
),
migrations.AlterField(
model_name='language',
name='common_name',
- field=models.CharField(help_text='Common name for the language. For example, the common name for C++03, C++11, and C++14 would be "C++".', max_length=10, verbose_name='common name'),
+ field=models.CharField(
+ help_text='Common name for the language. For example, the common name for C++03, C++11, and C++14 would be "C++".',
+ max_length=10,
+ verbose_name='common name',
+ ),
),
migrations.AlterField(
model_name='language',
name='description',
- field=models.TextField(blank=True, help_text='Use this field to inform users of quirks with your environment, additional restrictions, etc.', verbose_name='language description'),
+ field=models.TextField(
+ blank=True,
+ help_text='Use this field to inform users of quirks with your environment, additional restrictions, etc.',
+ verbose_name='language description',
+ ),
),
migrations.AlterField(
model_name='languagelimit',
name='memory_limit',
- field=models.IntegerField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1048576)], verbose_name='memory limit'),
+ field=models.IntegerField(
+ validators=[
+ django.core.validators.MinValueValidator(0),
+ django.core.validators.MaxValueValidator(1048576),
+ ],
+ verbose_name='memory limit',
+ ),
),
migrations.AlterField(
model_name='languagelimit',
name='time_limit',
- field=models.FloatField(validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(60)], verbose_name='time limit'),
+ field=models.FloatField(
+ validators=[
+ django.core.validators.MinValueValidator(0),
+ django.core.validators.MaxValueValidator(60),
+ ],
+ verbose_name='time limit',
+ ),
),
migrations.AlterField(
model_name='license',
name='display',
- field=models.CharField(blank=True, help_text='Displayed on pages under this license.', max_length=256, verbose_name='short name'),
+ field=models.CharField(
+ blank=True,
+ help_text='Displayed on pages under this license.',
+ max_length=256,
+ verbose_name='short name',
+ ),
),
migrations.AlterField(
model_name='license',
name='icon',
- field=models.CharField(blank=True, help_text='URL to the icon.', max_length=256, verbose_name='icon'),
+ field=models.CharField(
+ blank=True,
+ help_text='URL to the icon.',
+ max_length=256,
+ verbose_name='icon',
+ ),
),
migrations.AlterField(
model_name='miscconfig',
@@ -232,102 +412,208 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='organization',
name='access_code',
- field=models.CharField(blank=True, help_text='Student access code.', max_length=7, null=True, verbose_name='access code'),
+ field=models.CharField(
+ blank=True,
+ help_text='Student access code.',
+ max_length=7,
+ null=True,
+ verbose_name='access code',
+ ),
),
migrations.AlterField(
model_name='organization',
name='admins',
- field=models.ManyToManyField(help_text='Those who can edit this organization.', related_name='admin_of', to='judge.Profile', verbose_name='administrators'),
+ field=models.ManyToManyField(
+ help_text='Those who can edit this organization.',
+ related_name='admin_of',
+ to='judge.Profile',
+ verbose_name='administrators',
+ ),
),
migrations.AlterField(
model_name='organization',
name='class_required',
- field=models.BooleanField(default=False, help_text='Whether members are compelled to select a class when joining.', verbose_name='class membership required'),
+ field=models.BooleanField(
+ default=False,
+ help_text='Whether members are compelled to select a class when joining.',
+ verbose_name='class membership required',
+ ),
),
migrations.AlterField(
model_name='organization',
name='is_open',
- field=models.BooleanField(default=True, help_text='Allow joining organization.', verbose_name='is open organization?'),
+ field=models.BooleanField(
+ default=True,
+ help_text='Allow joining organization.',
+ verbose_name='is open organization?',
+ ),
),
migrations.AlterField(
model_name='organization',
name='logo_override_image',
- field=models.CharField(blank=True, default='', help_text='This image will replace the default site logo for users viewing the organization.', max_length=150, verbose_name='logo override image'),
+ field=models.CharField(
+ blank=True,
+ default='',
+ help_text='This image will replace the default site logo for users viewing the organization.',
+ max_length=150,
+ verbose_name='logo override image',
+ ),
),
migrations.AlterField(
model_name='organization',
name='short_name',
- field=models.CharField(help_text='Displayed beside user name during contests.', max_length=20, verbose_name='short name'),
+ field=models.CharField(
+ help_text='Displayed beside user name during contests.',
+ max_length=20,
+ verbose_name='short name',
+ ),
),
migrations.AlterField(
model_name='organization',
name='slots',
- field=models.IntegerField(blank=True, help_text='Maximum amount of users in this organization, only applicable to private organizations.', null=True, verbose_name='maximum size'),
+ field=models.IntegerField(
+ blank=True,
+ help_text='Maximum amount of users in this organization, only applicable to private organizations.',
+ null=True,
+ verbose_name='maximum size',
+ ),
),
migrations.AlterField(
model_name='organization',
name='slug',
- field=models.SlugField(help_text='Organization name shown in URLs.', max_length=128, verbose_name='organization slug'),
+ field=models.SlugField(
+ help_text='Organization name shown in URLs.',
+ max_length=128,
+ verbose_name='organization slug',
+ ),
),
migrations.AlterField(
model_name='problem',
name='allowed_languages',
- field=models.ManyToManyField(help_text='List of allowed submission languages.', to='judge.Language', verbose_name='allowed languages'),
+ field=models.ManyToManyField(
+ help_text='List of allowed submission languages.',
+ to='judge.Language',
+ verbose_name='allowed languages',
+ ),
),
migrations.AlterField(
model_name='problem',
name='authors',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to edit the problem, and be listed as authors.', related_name='authored_problems', to='judge.Profile', verbose_name='creators'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to edit the problem, and be listed as authors.',
+ related_name='authored_problems',
+ to='judge.Profile',
+ verbose_name='creators',
+ ),
),
migrations.AlterField(
model_name='problem',
name='code',
- field=models.CharField(help_text='A short, unique code for the problem, used in the URL after /problem/', max_length=20, unique=True, validators=[django.core.validators.RegexValidator('^[a-z0-9]+$', 'Problem code must be ^[a-z0-9]+$')], verbose_name='problem code'),
+ field=models.CharField(
+ help_text='A short, unique code for the problem, used in the URL after /problem/',
+ max_length=20,
+ unique=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[a-z0-9]+$', 'Problem code must be ^[a-z0-9]+$'
+ )
+ ],
+ verbose_name='problem code',
+ ),
),
migrations.AlterField(
model_name='problem',
name='curators',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to edit the problem, but not be listed as authors.', related_name='curated_problems', to='judge.Profile', verbose_name='curators'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to edit the problem, but not be listed as authors.',
+ related_name='curated_problems',
+ to='judge.Profile',
+ verbose_name='curators',
+ ),
),
migrations.AlterField(
model_name='problem',
name='date',
- field=models.DateTimeField(blank=True, db_index=True, help_text="Doesn't have the magic ability to auto-publish due to backward compatibility.", null=True, verbose_name='date of publishing'),
+ field=models.DateTimeField(
+ blank=True,
+ db_index=True,
+ help_text="Doesn't have the magic ability to auto-publish due to backward compatibility.",
+ null=True,
+ verbose_name='date of publishing',
+ ),
),
migrations.AlterField(
model_name='problem',
name='description',
- field=models.TextField(validators=[judge.models.problem.disallowed_characters_validator], verbose_name='problem body'),
+ field=models.TextField(
+ validators=[judge.models.problem.disallowed_characters_validator],
+ verbose_name='problem body',
+ ),
),
migrations.AlterField(
model_name='problem',
name='group',
- field=models.ForeignKey(help_text='The group of problem, shown under Category in the problem list.', on_delete=django.db.models.deletion.CASCADE, to='judge.problemgroup', verbose_name='problem group'),
+ field=models.ForeignKey(
+ help_text='The group of problem, shown under Category in the problem list.',
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.problemgroup',
+ verbose_name='problem group',
+ ),
),
migrations.AlterField(
model_name='problem',
name='is_manually_managed',
- field=models.BooleanField(db_index=True, default=False, help_text='Whether judges should be allowed to manage data or not.', verbose_name='manually managed'),
+ field=models.BooleanField(
+ db_index=True,
+ default=False,
+ help_text='Whether judges should be allowed to manage data or not.',
+ verbose_name='manually managed',
+ ),
),
migrations.AlterField(
model_name='problem',
name='license',
- field=models.ForeignKey(blank=True, help_text='The license under which this problem is published.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='judge.license', verbose_name='license'),
+ field=models.ForeignKey(
+ blank=True,
+ help_text='The license under which this problem is published.',
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to='judge.license',
+ verbose_name='license',
+ ),
),
migrations.AlterField(
model_name='problem',
name='memory_limit',
- field=models.PositiveIntegerField(help_text='The memory limit for this problem, in kilobytes (e.g. 256mb = 262144 kilobytes).', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1048576)], verbose_name='memory limit'),
+ field=models.PositiveIntegerField(
+ help_text='The memory limit for this problem, in kilobytes (e.g. 256mb = 262144 kilobytes).',
+ validators=[
+ django.core.validators.MinValueValidator(0),
+ django.core.validators.MaxValueValidator(1048576),
+ ],
+ verbose_name='memory limit',
+ ),
),
migrations.AlterField(
model_name='problem',
name='name',
- field=models.CharField(db_index=True, help_text='The full name of the problem, as shown in the problem list.', max_length=100, verbose_name='problem name'),
+ field=models.CharField(
+ db_index=True,
+ help_text='The full name of the problem, as shown in the problem list.',
+ max_length=100,
+ verbose_name='problem name',
+ ),
),
migrations.AlterField(
model_name='problem',
name='points',
- field=models.FloatField(help_text="Points awarded for problem completion. Points are displayed with a 'p' suffix if partial.", validators=[django.core.validators.MinValueValidator(0)], verbose_name='points'),
+ field=models.FloatField(
+ help_text="Points awarded for problem completion. Points are displayed with a 'p' suffix if partial.",
+ validators=[django.core.validators.MinValueValidator(0)],
+ verbose_name='points',
+ ),
),
migrations.AlterField(
model_name='problem',
@@ -337,126 +623,991 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='problem',
name='testers',
- field=models.ManyToManyField(blank=True, help_text='These users will be able to view the private problem, but not edit it.', related_name='tested_problems', to='judge.Profile', verbose_name='testers'),
+ field=models.ManyToManyField(
+ blank=True,
+ help_text='These users will be able to view the private problem, but not edit it.',
+ related_name='tested_problems',
+ to='judge.Profile',
+ verbose_name='testers',
+ ),
),
migrations.AlterField(
model_name='problem',
name='time_limit',
- field=models.FloatField(help_text='The time limit for this problem, in seconds. Fractional seconds (e.g. 1.5) are supported.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(60)], verbose_name='time limit'),
+ field=models.FloatField(
+ help_text='The time limit for this problem, in seconds. Fractional seconds (e.g. 1.5) are supported.',
+ validators=[
+ django.core.validators.MinValueValidator(0),
+ django.core.validators.MaxValueValidator(60),
+ ],
+ verbose_name='time limit',
+ ),
),
migrations.AlterField(
model_name='problem',
name='types',
- field=models.ManyToManyField(help_text="The type of problem, as shown on the problem's page.", to='judge.ProblemType', verbose_name='problem types'),
+ field=models.ManyToManyField(
+ help_text="The type of problem, as shown on the problem's page.",
+ to='judge.ProblemType',
+ verbose_name='problem types',
+ ),
),
migrations.AlterField(
model_name='problemclarification',
name='description',
- field=models.TextField(validators=[judge.models.problem.disallowed_characters_validator], verbose_name='clarification body'),
+ field=models.TextField(
+ validators=[judge.models.problem.disallowed_characters_validator],
+ verbose_name='clarification body',
+ ),
),
migrations.AlterField(
model_name='problemdata',
name='checker_args',
- field=models.TextField(blank=True, help_text='Checker arguments as a JSON object.', verbose_name='checker arguments'),
+ field=models.TextField(
+ blank=True,
+ help_text='Checker arguments as a JSON object.',
+ verbose_name='checker arguments',
+ ),
),
migrations.AlterField(
model_name='problemtestcase',
name='checker_args',
- field=models.TextField(blank=True, help_text='Checker arguments as a JSON object.', verbose_name='checker arguments'),
+ field=models.TextField(
+ blank=True,
+ help_text='Checker arguments as a JSON object.',
+ verbose_name='checker arguments',
+ ),
),
migrations.AlterField(
model_name='problemtranslation',
name='description',
- field=models.TextField(validators=[judge.models.problem.disallowed_characters_validator], verbose_name='translated description'),
+ field=models.TextField(
+ validators=[judge.models.problem.disallowed_characters_validator],
+ verbose_name='translated description',
+ ),
),
migrations.AlterField(
model_name='problemtranslation',
name='language',
- field=models.CharField(choices=[('ca', 'Catalan'), ('de', 'German'), ('el', 'Greek'), ('en', 'English'), ('es', 'Spanish'), ('fr', 'French'), ('hr', 'Croatian'), ('hu', 'Hungarian'), ('ja', 'Japanese'), ('ko', 'Korean'), ('pt', 'Brazilian Portuguese'), ('ro', 'Romanian'), ('ru', 'Russian'), ('sr-latn', 'Serbian (Latin)'), ('tr', 'Turkish'), ('vi', 'Vietnamese'), ('zh-hans', 'Simplified Chinese'), ('zh-hant', 'Traditional Chinese')], max_length=7, verbose_name='language'),
+ field=models.CharField(
+ choices=[
+ ('ca', 'Catalan'),
+ ('de', 'German'),
+ ('el', 'Greek'),
+ ('en', 'English'),
+ ('es', 'Spanish'),
+ ('fr', 'French'),
+ ('hr', 'Croatian'),
+ ('hu', 'Hungarian'),
+ ('ja', 'Japanese'),
+ ('ko', 'Korean'),
+ ('pt', 'Brazilian Portuguese'),
+ ('ro', 'Romanian'),
+ ('ru', 'Russian'),
+ ('sr-latn', 'Serbian (Latin)'),
+ ('tr', 'Turkish'),
+ ('vi', 'Vietnamese'),
+ ('zh-hans', 'Simplified Chinese'),
+ ('zh-hant', 'Traditional Chinese'),
+ ],
+ max_length=7,
+ verbose_name='language',
+ ),
),
migrations.AlterField(
model_name='profile',
name='ace_theme',
- field=models.CharField(choices=[('ambiance', 'Ambiance'), ('chaos', 'Chaos'), ('chrome', 'Chrome'), ('clouds', 'Clouds'), ('clouds_midnight', 'Clouds Midnight'), ('cobalt', 'Cobalt'), ('crimson_editor', 'Crimson Editor'), ('dawn', 'Dawn'), ('dreamweaver', 'Dreamweaver'), ('eclipse', 'Eclipse'), ('github', 'Github'), ('idle_fingers', 'Idle Fingers'), ('katzenmilch', 'Katzenmilch'), ('kr_theme', 'KR Theme'), ('kuroir', 'Kuroir'), ('merbivore', 'Merbivore'), ('merbivore_soft', 'Merbivore Soft'), ('mono_industrial', 'Mono Industrial'), ('monokai', 'Monokai'), ('pastel_on_dark', 'Pastel on Dark'), ('solarized_dark', 'Solarized Dark'), ('solarized_light', 'Solarized Light'), ('terminal', 'Terminal'), ('textmate', 'Textmate'), ('tomorrow', 'Tomorrow'), ('tomorrow_night', 'Tomorrow Night'), ('tomorrow_night_blue', 'Tomorrow Night Blue'), ('tomorrow_night_bright', 'Tomorrow Night Bright'), ('tomorrow_night_eighties', 'Tomorrow Night Eighties'), ('twilight', 'Twilight'), ('vibrant_ink', 'Vibrant Ink'), ('xcode', 'XCode')], default='github', max_length=30, verbose_name='Ace theme'),
+ field=models.CharField(
+ choices=[
+ ('ambiance', 'Ambiance'),
+ ('chaos', 'Chaos'),
+ ('chrome', 'Chrome'),
+ ('clouds', 'Clouds'),
+ ('clouds_midnight', 'Clouds Midnight'),
+ ('cobalt', 'Cobalt'),
+ ('crimson_editor', 'Crimson Editor'),
+ ('dawn', 'Dawn'),
+ ('dreamweaver', 'Dreamweaver'),
+ ('eclipse', 'Eclipse'),
+ ('github', 'Github'),
+ ('idle_fingers', 'Idle Fingers'),
+ ('katzenmilch', 'Katzenmilch'),
+ ('kr_theme', 'KR Theme'),
+ ('kuroir', 'Kuroir'),
+ ('merbivore', 'Merbivore'),
+ ('merbivore_soft', 'Merbivore Soft'),
+ ('mono_industrial', 'Mono Industrial'),
+ ('monokai', 'Monokai'),
+ ('pastel_on_dark', 'Pastel on Dark'),
+ ('solarized_dark', 'Solarized Dark'),
+ ('solarized_light', 'Solarized Light'),
+ ('terminal', 'Terminal'),
+ ('textmate', 'Textmate'),
+ ('tomorrow', 'Tomorrow'),
+ ('tomorrow_night', 'Tomorrow Night'),
+ ('tomorrow_night_blue', 'Tomorrow Night Blue'),
+ ('tomorrow_night_bright', 'Tomorrow Night Bright'),
+ ('tomorrow_night_eighties', 'Tomorrow Night Eighties'),
+ ('twilight', 'Twilight'),
+ ('vibrant_ink', 'Vibrant Ink'),
+ ('xcode', 'XCode'),
+ ],
+ default='github',
+ max_length=30,
+ verbose_name='Ace theme',
+ ),
),
migrations.AlterField(
model_name='profile',
name='api_token',
- field=models.CharField(help_text='64-character hex-encoded API access token.', max_length=64, null=True, validators=[django.core.validators.RegexValidator('^[a-f0-9]{64}$', 'API token must be None or hexadecimal')], verbose_name='API token'),
+ field=models.CharField(
+ help_text='64-character hex-encoded API access token.',
+ max_length=64,
+ null=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^[a-f0-9]{64}$', 'API token must be None or hexadecimal'
+ )
+ ],
+ verbose_name='API token',
+ ),
),
migrations.AlterField(
model_name='profile',
name='is_totp_enabled',
- field=models.BooleanField(default=False, help_text='Check to enable TOTP-based two-factor authentication.', verbose_name='TOTP 2FA enabled'),
+ field=models.BooleanField(
+ default=False,
+ help_text='Check to enable TOTP-based two-factor authentication.',
+ verbose_name='TOTP 2FA enabled',
+ ),
),
migrations.AlterField(
model_name='profile',
name='is_webauthn_enabled',
- field=models.BooleanField(default=False, help_text='Check to enable WebAuthn-based two-factor authentication.', verbose_name='WebAuthn 2FA enabled'),
+ field=models.BooleanField(
+ default=False,
+ help_text='Check to enable WebAuthn-based two-factor authentication.',
+ verbose_name='WebAuthn 2FA enabled',
+ ),
),
migrations.AlterField(
model_name='profile',
name='math_engine',
- field=models.CharField(choices=[('tex', 'Leave as LaTeX'), ('svg', 'SVG with PNG fallback'), ('mml', 'MathML only'), ('jax', 'MathJax with SVG/PNG fallback'), ('auto', 'Detect best quality')], default='auto', help_text='The rendering engine used to render math.', max_length=4, verbose_name='math engine'),
+ field=models.CharField(
+ choices=[
+ ('tex', 'Leave as LaTeX'),
+ ('svg', 'SVG with PNG fallback'),
+ ('mml', 'MathML only'),
+ ('jax', 'MathJax with SVG/PNG fallback'),
+ ('auto', 'Detect best quality'),
+ ],
+ default='auto',
+ help_text='The rendering engine used to render math.',
+ max_length=4,
+ verbose_name='math engine',
+ ),
),
migrations.AlterField(
model_name='profile',
name='scratch_codes',
- field=judge.models.profile.EncryptedNullCharField(blank=True, help_text='JSON array of 16-character Base32-encoded codes for scratch codes.', max_length=255, null=True, validators=[django.core.validators.RegexValidator('^(\\[\\])?$|^\\[("[A-Z0-9]{16}", *)*"[A-Z0-9]{16}"\\]$', 'Scratch codes must be empty or a JSON array of 16-character Base32 codes.')], verbose_name='scratch codes'),
+ field=judge.models.profile.EncryptedNullCharField(
+ blank=True,
+ help_text='JSON array of 16-character Base32-encoded codes for scratch codes.',
+ max_length=255,
+ null=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^(\\[\\])?$|^\\[("[A-Z0-9]{16}", *)*"[A-Z0-9]{16}"\\]$',
+ 'Scratch codes must be empty or a JSON array of 16-character Base32 codes.',
+ )
+ ],
+ verbose_name='scratch codes',
+ ),
),
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='America/Toronto', max_length=50, verbose_name='time zone'),
+ 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='America/Toronto',
+ max_length=50,
+ verbose_name='time zone',
+ ),
),
migrations.AlterField(
model_name='profile',
name='totp_key',
- field=judge.models.profile.EncryptedNullCharField(blank=True, help_text='32-character Base32-encoded key for TOTP.', max_length=32, null=True, validators=[django.core.validators.RegexValidator('^$|^[A-Z2-7]{32}$', 'TOTP key must be empty or Base32.')], verbose_name='TOTP key'),
+ field=judge.models.profile.EncryptedNullCharField(
+ blank=True,
+ help_text='32-character Base32-encoded key for TOTP.',
+ max_length=32,
+ null=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ '^$|^[A-Z2-7]{32}$', 'TOTP key must be empty or Base32.'
+ )
+ ],
+ verbose_name='TOTP key',
+ ),
),
migrations.AlterField(
model_name='profile',
name='username_display_override',
- field=models.CharField(blank=True, help_text='Name displayed in place of username.', max_length=100, verbose_name='display name override'),
+ field=models.CharField(
+ blank=True,
+ help_text='Name displayed in place of username.',
+ max_length=100,
+ verbose_name='display name override',
+ ),
),
migrations.AlterField(
model_name='solution',
name='content',
- field=models.TextField(validators=[judge.models.problem.disallowed_characters_validator], verbose_name='editorial content'),
+ field=models.TextField(
+ validators=[judge.models.problem.disallowed_characters_validator],
+ verbose_name='editorial content',
+ ),
),
migrations.AlterField(
model_name='submission',
name='problem',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.problem', verbose_name='problem'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.problem',
+ verbose_name='problem',
+ ),
),
migrations.AlterField(
model_name='submission',
name='result',
- field=models.CharField(blank=True, choices=[('AC', 'Accepted'), ('WA', 'Wrong Answer'), ('TLE', 'Time Limit Exceeded'), ('MLE', 'Memory Limit Exceeded'), ('OLE', 'Output Limit Exceeded'), ('IR', 'Invalid Return'), ('RTE', 'Runtime Error'), ('CE', 'Compile Error'), ('IE', 'Internal Error'), ('SC', 'Short Circuited'), ('AB', 'Aborted')], db_index=True, default=None, max_length=3, null=True, verbose_name='result'),
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ('AC', 'Accepted'),
+ ('WA', 'Wrong Answer'),
+ ('TLE', 'Time Limit Exceeded'),
+ ('MLE', 'Memory Limit Exceeded'),
+ ('OLE', 'Output Limit Exceeded'),
+ ('IR', 'Invalid Return'),
+ ('RTE', 'Runtime Error'),
+ ('CE', 'Compile Error'),
+ ('IE', 'Internal Error'),
+ ('SC', 'Short Circuited'),
+ ('AB', 'Aborted'),
+ ],
+ db_index=True,
+ default=None,
+ max_length=3,
+ null=True,
+ verbose_name='result',
+ ),
),
migrations.AlterField(
model_name='submission',
name='user',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='judge.profile', verbose_name='user'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.profile',
+ verbose_name='user',
+ ),
),
migrations.AlterField(
model_name='submissiontestcase',
name='status',
- field=models.CharField(choices=[('AC', 'Accepted'), ('WA', 'Wrong Answer'), ('TLE', 'Time Limit Exceeded'), ('MLE', 'Memory Limit Exceeded'), ('OLE', 'Output Limit Exceeded'), ('IR', 'Invalid Return'), ('RTE', 'Runtime Error'), ('CE', 'Compile Error'), ('IE', 'Internal Error'), ('SC', 'Short Circuited'), ('AB', 'Aborted')], max_length=3, verbose_name='status flag'),
+ field=models.CharField(
+ choices=[
+ ('AC', 'Accepted'),
+ ('WA', 'Wrong Answer'),
+ ('TLE', 'Time Limit Exceeded'),
+ ('MLE', 'Memory Limit Exceeded'),
+ ('OLE', 'Output Limit Exceeded'),
+ ('IR', 'Invalid Return'),
+ ('RTE', 'Runtime Error'),
+ ('CE', 'Compile Error'),
+ ('IE', 'Internal Error'),
+ ('SC', 'Short Circuited'),
+ ('AB', 'Aborted'),
+ ],
+ max_length=3,
+ verbose_name='status flag',
+ ),
),
migrations.AlterField(
model_name='ticketmessage',
name='user',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_messages', to='judge.profile', verbose_name='user'),
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='ticket_messages',
+ to='judge.profile',
+ verbose_name='user',
+ ),
),
migrations.AddField(
model_name='problemdata',
name='nobigmath',
- field=models.BooleanField(blank=True, null=True, verbose_name='disable biginteger/bigdecimal'),
+ field=models.BooleanField(
+ blank=True, null=True, verbose_name='disable biginteger/bigdecimal'
+ ),
),
migrations.AddField(
model_name='problemdata',
name='unicode',
- field=models.BooleanField(blank=True, null=True, verbose_name='enable unicode'),
+ field=models.BooleanField(
+ blank=True, null=True, verbose_name='enable unicode'
+ ),
),
]
diff --git a/judge/migrations/0134_add_voting_functionality.py b/judge/migrations/0134_add_voting_functionality.py
index c622304ea2..6219ba3009 100644
--- a/judge/migrations/0134_add_voting_functionality.py
+++ b/judge/migrations/0134_add_voting_functionality.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0133_add_problem_data_hints'),
]
@@ -13,28 +12,71 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='profile',
name='is_banned_from_problem_voting',
- field=models.BooleanField(default=False,
- help_text="User will not be able to vote on problems' point values.",
- verbose_name='banned from voting on problem point values'),
+ field=models.BooleanField(
+ default=False,
+ help_text="User will not be able to vote on problems' point values.",
+ verbose_name='banned from voting on problem point values',
+ ),
),
migrations.CreateModel(
name='ProblemPointsVote',
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('points', models.IntegerField(help_text='The amount of points the voter thinks this problem deserves.',
- validators=[django.core.validators.MinValueValidator(1),
- django.core.validators.MaxValueValidator(50)],
- verbose_name='proposed points')),
- ('note', models.TextField(blank=True, default='', help_text='Justification for problem point value.',
- max_length=8192, verbose_name='note')),
- ('problem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
- related_name='problem_points_votes', to='judge.Problem',
- verbose_name='problem')),
- ('voter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
- related_name='problem_points_votes', to='judge.Profile',
- verbose_name='voter')),
- ('vote_time', models.DateTimeField(auto_now_add=True, help_text='The time this vote was cast.',
- verbose_name='vote time')),
+ (
+ 'id',
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name='ID',
+ ),
+ ),
+ (
+ 'points',
+ models.IntegerField(
+ help_text='The amount of points the voter thinks this problem deserves.',
+ validators=[
+ django.core.validators.MinValueValidator(1),
+ django.core.validators.MaxValueValidator(50),
+ ],
+ verbose_name='proposed points',
+ ),
+ ),
+ (
+ 'note',
+ models.TextField(
+ blank=True,
+ default='',
+ help_text='Justification for problem point value.',
+ max_length=8192,
+ verbose_name='note',
+ ),
+ ),
+ (
+ 'problem',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='problem_points_votes',
+ to='judge.Problem',
+ verbose_name='problem',
+ ),
+ ),
+ (
+ 'voter',
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='problem_points_votes',
+ to='judge.Profile',
+ verbose_name='voter',
+ ),
+ ),
+ (
+ 'vote_time',
+ models.DateTimeField(
+ auto_now_add=True,
+ help_text='The time this vote was cast.',
+ verbose_name='vote time',
+ ),
+ ),
],
options={
'verbose_name': 'problem vote',
diff --git a/judge/migrations/0135_disable_judge.py b/judge/migrations/0135_disable_judge.py
index 5d745feb26..16fcd9486f 100644
--- a/judge/migrations/0135_disable_judge.py
+++ b/judge/migrations/0135_disable_judge.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0134_add_voting_functionality'),
]
@@ -13,6 +12,10 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='judge',
name='is_disabled',
- field=models.BooleanField(default=False, help_text='Whether this judge should be removed from judging queue.', verbose_name='disable judge'),
+ field=models.BooleanField(
+ default=False,
+ help_text='Whether this judge should be removed from judging queue.',
+ verbose_name='disable judge',
+ ),
),
]
diff --git a/judge/migrations/0136_remove_zombie_editorials.py b/judge/migrations/0136_remove_zombie_editorials.py
index 418c16ee5b..4dd3c7d7fb 100644
--- a/judge/migrations/0136_remove_zombie_editorials.py
+++ b/judge/migrations/0136_remove_zombie_editorials.py
@@ -8,7 +8,6 @@ def delete_null_solutions(apps, scheme_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0135_disable_judge'),
]
@@ -18,6 +17,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='solution',
name='problem',
- field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='solution', to='judge.Problem', verbose_name='associated problem'),
+ field=models.OneToOneField(
+ blank=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='solution',
+ to='judge.Problem',
+ verbose_name='associated problem',
+ ),
),
]
diff --git a/judge/migrations/0137_profile_site_theme.py b/judge/migrations/0137_profile_site_theme.py
index 5cf2ba411e..0b4613a0d7 100644
--- a/judge/migrations/0137_profile_site_theme.py
+++ b/judge/migrations/0137_profile_site_theme.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0136_remove_zombie_editorials'),
]
@@ -13,6 +12,15 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='profile',
name='site_theme',
- field=models.CharField(choices=[('auto', 'Follow system default'), ('light', 'Light'), ('dark', 'Dark')], default='auto', max_length=10, verbose_name='site theme'),
+ field=models.CharField(
+ choices=[
+ ('auto', 'Follow system default'),
+ ('light', 'Light'),
+ ('dark', 'Dark'),
+ ],
+ default='auto',
+ max_length=10,
+ verbose_name='site theme',
+ ),
),
]
diff --git a/judge/migrations/0138_dark_ace_theme.py b/judge/migrations/0138_dark_ace_theme.py
index 110e873f82..c08c038d44 100644
--- a/judge/migrations/0138_dark_ace_theme.py
+++ b/judge/migrations/0138_dark_ace_theme.py
@@ -14,7 +14,6 @@ def auto_to_github(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0137_profile_site_theme'),
]
@@ -23,7 +22,46 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='profile',
name='ace_theme',
- field=models.CharField(choices=[('auto', 'Follow theme default'), ('ambiance', 'Ambiance'), ('chaos', 'Chaos'), ('chrome', 'Chrome'), ('clouds', 'Clouds'), ('clouds_midnight', 'Clouds Midnight'), ('cobalt', 'Cobalt'), ('crimson_editor', 'Crimson Editor'), ('dawn', 'Dawn'), ('dreamweaver', 'Dreamweaver'), ('eclipse', 'Eclipse'), ('github', 'Github'), ('idle_fingers', 'Idle Fingers'), ('katzenmilch', 'Katzenmilch'), ('kr_theme', 'KR Theme'), ('kuroir', 'Kuroir'), ('merbivore', 'Merbivore'), ('merbivore_soft', 'Merbivore Soft'), ('mono_industrial', 'Mono Industrial'), ('monokai', 'Monokai'), ('pastel_on_dark', 'Pastel on Dark'), ('solarized_dark', 'Solarized Dark'), ('solarized_light', 'Solarized Light'), ('terminal', 'Terminal'), ('textmate', 'Textmate'), ('tomorrow', 'Tomorrow'), ('tomorrow_night', 'Tomorrow Night'), ('tomorrow_night_blue', 'Tomorrow Night Blue'), ('tomorrow_night_bright', 'Tomorrow Night Bright'), ('tomorrow_night_eighties', 'Tomorrow Night Eighties'), ('twilight', 'Twilight'), ('vibrant_ink', 'Vibrant Ink'), ('xcode', 'XCode')], default='auto', max_length=30, verbose_name='Ace theme'),
+ field=models.CharField(
+ choices=[
+ ('auto', 'Follow theme default'),
+ ('ambiance', 'Ambiance'),
+ ('chaos', 'Chaos'),
+ ('chrome', 'Chrome'),
+ ('clouds', 'Clouds'),
+ ('clouds_midnight', 'Clouds Midnight'),
+ ('cobalt', 'Cobalt'),
+ ('crimson_editor', 'Crimson Editor'),
+ ('dawn', 'Dawn'),
+ ('dreamweaver', 'Dreamweaver'),
+ ('eclipse', 'Eclipse'),
+ ('github', 'Github'),
+ ('idle_fingers', 'Idle Fingers'),
+ ('katzenmilch', 'Katzenmilch'),
+ ('kr_theme', 'KR Theme'),
+ ('kuroir', 'Kuroir'),
+ ('merbivore', 'Merbivore'),
+ ('merbivore_soft', 'Merbivore Soft'),
+ ('mono_industrial', 'Mono Industrial'),
+ ('monokai', 'Monokai'),
+ ('pastel_on_dark', 'Pastel on Dark'),
+ ('solarized_dark', 'Solarized Dark'),
+ ('solarized_light', 'Solarized Light'),
+ ('terminal', 'Terminal'),
+ ('textmate', 'Textmate'),
+ ('tomorrow', 'Tomorrow'),
+ ('tomorrow_night', 'Tomorrow Night'),
+ ('tomorrow_night_blue', 'Tomorrow Night Blue'),
+ ('tomorrow_night_bright', 'Tomorrow Night Bright'),
+ ('tomorrow_night_eighties', 'Tomorrow Night Eighties'),
+ ('twilight', 'Twilight'),
+ ('vibrant_ink', 'Vibrant Ink'),
+ ('xcode', 'XCode'),
+ ],
+ default='auto',
+ max_length=30,
+ verbose_name='Ace theme',
+ ),
),
migrations.RunPython(github_to_auto, auto_to_github, atomic=True),
]
diff --git a/judge/migrations/0139_user_index_refactor.py b/judge/migrations/0139_user_index_refactor.py
index eda597e2d7..746ffcc9d3 100644
--- a/judge/migrations/0139_user_index_refactor.py
+++ b/judge/migrations/0139_user_index_refactor.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0138_dark_ace_theme'),
]
@@ -27,14 +26,22 @@ class Migration(migrations.Migration):
),
migrations.AddIndex(
model_name='profile',
- index=models.Index(fields=['is_unlisted', '-performance_points'], name='judge_profi_is_unli_1410d8_idx'),
+ index=models.Index(
+ fields=['is_unlisted', '-performance_points'],
+ name='judge_profi_is_unli_1410d8_idx',
+ ),
),
migrations.AddIndex(
model_name='profile',
- index=models.Index(fields=['is_unlisted', '-rating'], name='judge_profi_is_unli_bcf16a_idx'),
+ index=models.Index(
+ fields=['is_unlisted', '-rating'], name='judge_profi_is_unli_bcf16a_idx'
+ ),
),
migrations.AddIndex(
model_name='profile',
- index=models.Index(fields=['is_unlisted', '-problem_count'], name='judge_profi_is_unli_171bf3_idx'),
+ index=models.Index(
+ fields=['is_unlisted', '-problem_count'],
+ name='judge_profi_is_unli_171bf3_idx',
+ ),
),
]
diff --git a/judge/migrations/0140_submission_index_refactor.py b/judge/migrations/0140_submission_index_refactor.py
index 230c957c95..4c92258509 100644
--- a/judge/migrations/0140_submission_index_refactor.py
+++ b/judge/migrations/0140_submission_index_refactor.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0139_user_index_refactor'),
]
@@ -18,7 +17,26 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='submission',
name='result',
- field=models.CharField(blank=True, choices=[('AC', 'Accepted'), ('WA', 'Wrong Answer'), ('TLE', 'Time Limit Exceeded'), ('MLE', 'Memory Limit Exceeded'), ('OLE', 'Output Limit Exceeded'), ('IR', 'Invalid Return'), ('RTE', 'Runtime Error'), ('CE', 'Compile Error'), ('IE', 'Internal Error'), ('SC', 'Short Circuited'), ('AB', 'Aborted')], default=None, max_length=3, null=True, verbose_name='result'),
+ field=models.CharField(
+ blank=True,
+ choices=[
+ ('AC', 'Accepted'),
+ ('WA', 'Wrong Answer'),
+ ('TLE', 'Time Limit Exceeded'),
+ ('MLE', 'Memory Limit Exceeded'),
+ ('OLE', 'Output Limit Exceeded'),
+ ('IR', 'Invalid Return'),
+ ('RTE', 'Runtime Error'),
+ ('CE', 'Compile Error'),
+ ('IE', 'Internal Error'),
+ ('SC', 'Short Circuited'),
+ ('AB', 'Aborted'),
+ ],
+ default=None,
+ max_length=3,
+ null=True,
+ verbose_name='result',
+ ),
),
migrations.AlterField(
model_name='submission',
@@ -27,18 +45,28 @@ class Migration(migrations.Migration):
),
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['problem', 'user', '-points', '-time'], name='judge_submi_problem_8d5e0a_idx'),
+ index=models.Index(
+ fields=['problem', 'user', '-points', '-time'],
+ name='judge_submi_problem_8d5e0a_idx',
+ ),
),
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['result', '-id'], name='judge_submi_result_7a005c_idx'),
+ index=models.Index(
+ fields=['result', '-id'], name='judge_submi_result_7a005c_idx'
+ ),
),
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['result', 'language', '-id'], name='judge_submi_result_ba9a62_idx'),
+ index=models.Index(
+ fields=['result', 'language', '-id'],
+ name='judge_submi_result_ba9a62_idx',
+ ),
),
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['language', '-id'], name='judge_submi_languag_dfe850_idx'),
+ index=models.Index(
+ fields=['language', '-id'], name='judge_submi_languag_dfe850_idx'
+ ),
),
]
diff --git a/judge/migrations/0141_submission_extra_index.py b/judge/migrations/0141_submission_extra_index.py
index e32dbfcca0..d97f367c43 100644
--- a/judge/migrations/0141_submission_extra_index.py
+++ b/judge/migrations/0141_submission_extra_index.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0140_submission_index_refactor'),
]
@@ -12,22 +11,34 @@ class Migration(migrations.Migration):
operations = [
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['result', 'problem'], name='judge_submi_result_a77e42_idx'),
+ index=models.Index(
+ fields=['result', 'problem'], name='judge_submi_result_a77e42_idx'
+ ),
),
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['language', 'problem', 'result'], name='judge_submi_languag_380ab4_idx'),
+ index=models.Index(
+ fields=['language', 'problem', 'result'],
+ name='judge_submi_languag_380ab4_idx',
+ ),
),
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['problem', 'result'], name='judge_submi_problem_49f8ec_idx'),
+ index=models.Index(
+ fields=['problem', 'result'], name='judge_submi_problem_49f8ec_idx'
+ ),
),
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['user', 'problem', 'result'], name='judge_submi_user_id_650db3_idx'),
+ index=models.Index(
+ fields=['user', 'problem', 'result'],
+ name='judge_submi_user_id_650db3_idx',
+ ),
),
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['user', 'result'], name='judge_submi_user_id_ec9a4b_idx'),
+ index=models.Index(
+ fields=['user', 'result'], name='judge_submi_user_id_ec9a4b_idx'
+ ),
),
]
diff --git a/judge/migrations/0142_comment_revision_count.py b/judge/migrations/0142_comment_revision_count.py
index 370956b328..a809dfc1da 100644
--- a/judge/migrations/0142_comment_revision_count.py
+++ b/judge/migrations/0142_comment_revision_count.py
@@ -12,7 +12,8 @@ def populate_revisions(apps, schema_editor):
# Therefore, it's safe to all revision counts as zero.
pass
else:
- schema_editor.execute("""\
+ schema_editor.execute(
+ """\
UPDATE `judge_comment` INNER JOIN (
SELECT CAST(`reversion_version`.`object_id` AS INT) AS `id`, COUNT(*) AS `count`
FROM `reversion_version`
@@ -21,7 +22,9 @@ def populate_revisions(apps, schema_editor):
GROUP BY 1
) `versions` ON (`judge_comment`.`id` = `versions`.`id`)
SET `judge_comment`.`revisions` = `versions`.`count`;
-""", (content_type.id, db_alias))
+""",
+ (content_type.id, db_alias),
+ )
class Migration(migrations.Migration):
@@ -35,5 +38,7 @@ class Migration(migrations.Migration):
name='revisions',
field=models.IntegerField(default=0, verbose_name='revisions'),
),
- migrations.RunPython(populate_revisions, migrations.RunPython.noop, atomic=False, elidable=True),
+ migrations.RunPython(
+ populate_revisions, migrations.RunPython.noop, atomic=False, elidable=True
+ ),
]
diff --git a/judge/migrations/0143_contest_problem_rank_index.py b/judge/migrations/0143_contest_problem_rank_index.py
index 1985712685..973d144fdc 100644
--- a/judge/migrations/0143_contest_problem_rank_index.py
+++ b/judge/migrations/0143_contest_problem_rank_index.py
@@ -4,7 +4,6 @@
class Migration(migrations.Migration):
-
dependencies = [
('judge', '0142_comment_revision_count'),
]
@@ -12,6 +11,9 @@ class Migration(migrations.Migration):
operations = [
migrations.AddIndex(
model_name='submission',
- index=models.Index(fields=['contest_object', 'problem', 'user', '-points', '-time'], name='judge_submi_contest_59fbe3_idx'),
+ index=models.Index(
+ fields=['contest_object', 'problem', 'user', '-points', '-time'],
+ name='judge_submi_contest_59fbe3_idx',
+ ),
),
]
diff --git a/judge/migrations/0144_submission_index_cleanup.py b/judge/migrations/0144_submission_index_cleanup.py
index 778714e621..939cf09796 100644
--- a/judge/migrations/0144_submission_index_cleanup.py
+++ b/judge/migrations/0144_submission_index_cleanup.py
@@ -11,32 +11,55 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='submission',
name='contest_object',
- field=models.ForeignKey(blank=True, db_index=False, null=True, on_delete=django.db.models.deletion.SET_NULL,
- related_name='+', to='judge.contest', verbose_name='contest'),
+ field=models.ForeignKey(
+ blank=True,
+ db_index=False,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name='+',
+ to='judge.contest',
+ verbose_name='contest',
+ ),
),
migrations.AlterField(
model_name='submission',
name='language',
- field=models.ForeignKey(db_index=False, on_delete=django.db.models.deletion.CASCADE, to='judge.language',
- verbose_name='submission language'),
+ field=models.ForeignKey(
+ db_index=False,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.language',
+ verbose_name='submission language',
+ ),
),
migrations.AlterField(
model_name='submission',
name='problem',
- field=models.ForeignKey(db_index=False, on_delete=django.db.models.deletion.CASCADE, to='judge.problem',
- verbose_name='problem'),
+ field=models.ForeignKey(
+ db_index=False,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.problem',
+ verbose_name='problem',
+ ),
),
migrations.AlterField(
model_name='submission',
name='user',
- field=models.ForeignKey(db_index=False, on_delete=django.db.models.deletion.CASCADE, to='judge.profile',
- verbose_name='user'),
+ field=models.ForeignKey(
+ db_index=False,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='judge.profile',
+ verbose_name='user',
+ ),
),
migrations.AlterField(
model_name='submissiontestcase',
name='submission',
- field=models.ForeignKey(db_index=False, on_delete=django.db.models.deletion.CASCADE,
- related_name='test_cases', to='judge.submission',
- verbose_name='associated submission'),
+ field=models.ForeignKey(
+ db_index=False,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='test_cases',
+ to='judge.submission',
+ verbose_name='associated submission',
+ ),
),
]
diff --git a/judge/models/__init__.py b/judge/models/__init__.py
index 11621e5083..2c9d494aca 100644
--- a/judge/models/__init__.py
+++ b/judge/models/__init__.py
@@ -1,18 +1,56 @@
from reversion import revisions
-from judge.models.choices import ACE_THEMES, EFFECTIVE_MATH_ENGINES, MATH_ENGINES_CHOICES, TIMEZONE
+from judge.models.choices import (
+ ACE_THEMES,
+ EFFECTIVE_MATH_ENGINES,
+ MATH_ENGINES_CHOICES,
+ TIMEZONE,
+)
from judge.models.comment import Comment, CommentLock, CommentVote
-from judge.models.contest import Contest, ContestMoss, ContestParticipation, ContestProblem, ContestSubmission, \
- ContestTag, Rating
+from judge.models.contest import (
+ Contest,
+ ContestMoss,
+ ContestParticipation,
+ ContestProblem,
+ ContestSubmission,
+ ContestTag,
+ Rating,
+)
from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate_regex
-from judge.models.problem import LanguageLimit, License, Problem, ProblemClarification, ProblemGroup, \
- ProblemPointsVote, ProblemTranslation, ProblemType, Solution, SubmissionSourceAccess, \
- TranslatedProblemQuerySet
-from judge.models.problem_data import CHECKERS, ProblemData, ProblemTestCase, problem_data_storage, \
- problem_directory_file
-from judge.models.profile import Class, Organization, OrganizationRequest, Profile, WebAuthnCredential
+from judge.models.problem import (
+ LanguageLimit,
+ License,
+ Problem,
+ ProblemClarification,
+ ProblemGroup,
+ ProblemPointsVote,
+ ProblemTranslation,
+ ProblemType,
+ Solution,
+ SubmissionSourceAccess,
+ TranslatedProblemQuerySet,
+)
+from judge.models.problem_data import (
+ CHECKERS,
+ ProblemData,
+ ProblemTestCase,
+ problem_data_storage,
+ problem_directory_file,
+)
+from judge.models.profile import (
+ Class,
+ Organization,
+ OrganizationRequest,
+ Profile,
+ WebAuthnCredential,
+)
from judge.models.runtime import Judge, Language, RuntimeVersion
-from judge.models.submission import SUBMISSION_RESULT, Submission, SubmissionSource, SubmissionTestCase
+from judge.models.submission import (
+ SUBMISSION_RESULT,
+ Submission,
+ SubmissionSource,
+ SubmissionTestCase,
+)
from judge.models.ticket import Ticket, TicketMessage
revisions.register(Profile, exclude=['points', 'last_access', 'ip', 'rating'])
@@ -25,5 +63,7 @@
revisions.register(Solution)
revisions.register(Judge, fields=['name', 'created', 'auth_key', 'description'])
revisions.register(Language)
-revisions.register(Comment, fields=['author', 'time', 'page', 'score', 'body', 'hidden', 'parent'])
+revisions.register(
+ Comment, fields=['author', 'time', 'page', 'score', 'body', 'hidden', 'parent']
+)
del revisions
diff --git a/judge/models/comment.py b/judge/models/comment.py
index 0bc2b5b03c..7d4b1cf38f 100644
--- a/judge/models/comment.py
+++ b/judge/models/comment.py
@@ -19,20 +19,31 @@
__all__ = ['Comment', 'CommentLock', 'CommentVote']
-comment_validator = RegexValidator(r'^[pcs]:[a-z0-9]+$|^b:\d+$',
- _(r'Page code must be ^[pcs]:[a-z0-9]+$|^b:\d+$'))
+comment_validator = RegexValidator(
+ r'^[pcs]:[a-z0-9]+$|^b:\d+$', _(r'Page code must be ^[pcs]:[a-z0-9]+$|^b:\d+$')
+)
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])
+ page = models.CharField(
+ max_length=30,
+ verbose_name=_('associated page'),
+ db_index=True,
+ validators=[comment_validator],
+ )
score = models.IntegerField(verbose_name=_('votes'), default=0)
body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
hidden = models.BooleanField(verbose_name=_('hidden'), default=0)
- parent = TreeForeignKey('self', verbose_name=_('parent'), null=True, blank=True, related_name='replies',
- on_delete=CASCADE)
+ parent = TreeForeignKey(
+ 'self',
+ verbose_name=_('parent'),
+ null=True,
+ blank=True,
+ related_name='replies',
+ on_delete=CASCADE,
+ )
revisions = models.IntegerField(verbose_name=_('revisions'), default=0)
class Meta:
@@ -44,24 +55,43 @@ class MPTTMeta:
@classmethod
def most_recent(cls, user, n, batch=None):
- queryset = cls.objects.filter(hidden=False).select_related('author__user') \
- .defer('author__about', 'body').order_by('-id')
+ queryset = (
+ cls.objects.filter(hidden=False)
+ .select_related('author__user')
+ .defer('author__about', 'body')
+ .order_by('-id')
+ )
- problem_cache = CacheDict(lambda code: Problem.objects.defer('description', 'summary').get(code=code))
- solution_cache = CacheDict(lambda code: Solution.objects.defer('content').get(problem__code=code))
- contest_cache = CacheDict(lambda key: Contest.objects.defer('description').get(key=key))
- blog_cache = CacheDict(lambda id: BlogPost.objects.defer('summary', 'content').get(id=id))
+ problem_cache = CacheDict(
+ lambda code: Problem.objects.defer('description', 'summary').get(code=code)
+ )
+ solution_cache = CacheDict(
+ lambda code: Solution.objects.defer('content').get(problem__code=code)
+ )
+ contest_cache = CacheDict(
+ lambda key: Contest.objects.defer('description').get(key=key)
+ )
+ blog_cache = CacheDict(
+ lambda id: BlogPost.objects.defer('summary', 'content').get(id=id)
+ )
- problem_access = CacheDict(lambda code: problem_cache[code].is_accessible_by(user))
- solution_access = CacheDict(lambda code: problem_access[code] and solution_cache[code].is_accessible_by(user))
- contest_access = CacheDict(lambda key: contest_cache[key].is_accessible_by(user))
+ problem_access = CacheDict(
+ lambda code: problem_cache[code].is_accessible_by(user)
+ )
+ solution_access = CacheDict(
+ lambda code: problem_access[code]
+ and solution_cache[code].is_accessible_by(user)
+ )
+ contest_access = CacheDict(
+ lambda key: contest_cache[key].is_accessible_by(user)
+ )
blog_access = CacheDict(lambda id: blog_cache[id].can_see(user))
if batch is None:
batch = 2 * n
output = []
for i in itertools.count(0):
- slice = queryset[i * batch:i * batch + batch]
+ slice = queryset[i * batch : i * batch + batch]
if not slice:
break
for comment in slice:
@@ -72,7 +102,9 @@ def most_recent(cls, user, n, batch=None):
comment.page_title = problem_cache[page_key].name
elif comment.page.startswith('s:'):
has_access = solution_access[page_key]
- comment.page_title = _('Editorial for %s') % problem_cache[page_key].name
+ comment.page_title = (
+ _('Editorial for %s') % problem_cache[page_key].name
+ )
elif comment.page.startswith('c:'):
has_access = contest_access[page_key]
comment.page_title = contest_cache[page_key].name
@@ -124,7 +156,9 @@ def get_page_title(cls, page):
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 _('Editorial for %s') % Problem.objects.values_list(
+ 'name', flat=True
+ ).get(code=page[2:])
return ''
except ObjectDoesNotExist:
return ''
@@ -138,7 +172,9 @@ def is_accessible_by(self, user):
if self.page.startswith('p:'):
return Problem.objects.get(code=self.page[2:]).is_accessible_by(user)
elif self.page.startswith('s:'):
- return Solution.objects.get(problem__code=self.page[2:]).is_accessible_by(user)
+ return Solution.objects.get(
+ problem__code=self.page[2:]
+ ).is_accessible_by(user)
elif self.page.startswith('c:'):
return Contest.objects.get(key=self.page[2:]).is_accessible_by(user)
elif self.page.startswith('b:'):
@@ -152,7 +188,10 @@ def get_absolute_url(self):
return '%s#comment-%d' % (self.link, self.id)
def __str__(self):
- return _('%(page)s by %(user)s') % {'page': self.page, 'user': self.author.user.username}
+ 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)))
@@ -179,13 +218,15 @@ class Meta:
class CommentLock(models.Model):
- page = models.CharField(max_length=30, verbose_name=_('associated page'), db_index=True,
- validators=[comment_validator])
+ page = models.CharField(
+ max_length=30,
+ verbose_name=_('associated page'),
+ db_index=True,
+ validators=[comment_validator],
+ )
class Meta:
- permissions = (
- ('override_comment_lock', _('Override comment lock')),
- )
+ permissions = (('override_comment_lock', _('Override comment lock')),)
verbose_name = _('comment lock')
verbose_name_plural = _('comment locks')
diff --git a/judge/models/contest.py b/judge/models/contest.py
index 37f2010a47..e5f2361265 100644
--- a/judge/models/contest.py
+++ b/judge/models/contest.py
@@ -16,7 +16,14 @@
from judge.models.submission import Submission
from judge.ratings import rate_contest
-__all__ = ['Contest', 'ContestTag', 'ContestParticipation', 'ContestProblem', 'ContestSubmission', 'Rating']
+__all__ = [
+ 'Contest',
+ 'ContestTag',
+ 'ContestParticipation',
+ 'ContestProblem',
+ 'ContestSubmission',
+ 'Rating',
+]
class MinValueOrNoneValidator(MinValueValidator):
@@ -27,9 +34,19 @@ def compare(self, a, b):
class ContestTag(models.Model):
color_validator = RegexValidator('^#(?:[A-Fa-f0-9]{3}){1,2}$', _('Invalid colour.'))
- name = models.CharField(max_length=20, verbose_name=_('tag name'), unique=True,
- validators=[RegexValidator(r'^[a-z-]+$', message=_('Lowercase letters and hyphens only.'))])
- color = models.CharField(max_length=7, verbose_name=_('tag colour'), validators=[color_validator])
+ name = models.CharField(
+ max_length=20,
+ verbose_name=_('tag name'),
+ unique=True,
+ validators=[
+ RegexValidator(
+ r'^[a-z-]+$', message=_('Lowercase letters and hyphens only.')
+ )
+ ],
+ )
+ color = models.CharField(
+ max_length=7, verbose_name=_('tag colour'), validators=[color_validator]
+ )
description = models.TextField(verbose_name=_('tag description'), blank=True)
def __str__(self):
@@ -45,7 +62,9 @@ def text_color(self, cache={}):
r, g, b = [ord(bytes.fromhex(i * 2)) for i in self.color[1:]]
else:
r, g, b = [i for i in bytes.fromhex(self.color[1:])]
- cache[self.color] = '#000' if 299 * r + 587 * g + 144 * b > 140000 else '#fff'
+ cache[self.color] = (
+ '#000' if 299 * r + 587 * g + 144 * b > 140000 else '#fff'
+ )
return cache[self.color]
class Meta:
@@ -64,117 +83,275 @@ class Contest(models.Model):
(SCOREBOARD_AFTER_PARTICIPATION, _('Hidden for duration of participation')),
(SCOREBOARD_HIDDEN, _('Hidden permanently')),
)
- key = models.CharField(max_length=20, verbose_name=_('contest id'), unique=True,
- validators=[RegexValidator('^[a-z0-9]+$', _('Contest id must be ^[a-z0-9]+$'))])
- name = models.CharField(max_length=100, verbose_name=_('contest name'), db_index=True)
- authors = models.ManyToManyField(Profile, verbose_name=_('authors'),
- help_text=_('These users will be able to edit the contest.'),
- related_name='authored_contests')
- curators = models.ManyToManyField(Profile, verbose_name=_('curators'),
- help_text=_('These users will be able to edit the contest, '
- 'but will not be listed as authors.'),
- related_name='curated_contests', blank=True)
- testers = models.ManyToManyField(Profile, verbose_name=_('testers'),
- help_text=_('These users will be able to view the contest, but not edit it.'),
- blank=True, related_name='tested_contests')
- tester_see_scoreboard = models.BooleanField(verbose_name=_('testers see scoreboard'), default=False,
- help_text=_('If testers can see the scoreboard.'))
- tester_see_submissions = models.BooleanField(verbose_name=_('testers see submissions'), default=False,
- help_text=_('If testers can see in-contest submissions.'))
- spectators = models.ManyToManyField(Profile, verbose_name=_('spectators'),
- help_text=_('These users will be able to spectate the contest, '
- 'but not see the problems ahead of time.'),
- blank=True, related_name='spectated_contests')
+ key = models.CharField(
+ max_length=20,
+ verbose_name=_('contest id'),
+ unique=True,
+ validators=[RegexValidator('^[a-z0-9]+$', _('Contest id must be ^[a-z0-9]+$'))],
+ )
+ name = models.CharField(
+ max_length=100, verbose_name=_('contest name'), db_index=True
+ )
+ authors = models.ManyToManyField(
+ Profile,
+ verbose_name=_('authors'),
+ help_text=_('These users will be able to edit the contest.'),
+ related_name='authored_contests',
+ )
+ curators = models.ManyToManyField(
+ Profile,
+ verbose_name=_('curators'),
+ help_text=_(
+ 'These users will be able to edit the contest, '
+ 'but will not be listed as authors.'
+ ),
+ related_name='curated_contests',
+ blank=True,
+ )
+ testers = models.ManyToManyField(
+ Profile,
+ verbose_name=_('testers'),
+ help_text=_('These users will be able to view the contest, but not edit it.'),
+ blank=True,
+ related_name='tested_contests',
+ )
+ tester_see_scoreboard = models.BooleanField(
+ verbose_name=_('testers see scoreboard'),
+ default=False,
+ help_text=_('If testers can see the scoreboard.'),
+ )
+ tester_see_submissions = models.BooleanField(
+ verbose_name=_('testers see submissions'),
+ default=False,
+ help_text=_('If testers can see in-contest submissions.'),
+ )
+ spectators = models.ManyToManyField(
+ Profile,
+ verbose_name=_('spectators'),
+ help_text=_(
+ 'These users will be able to spectate the contest, '
+ 'but not see the problems ahead of time.'
+ ),
+ blank=True,
+ related_name='spectated_contests',
+ )
description = models.TextField(verbose_name=_('description'), blank=True)
- problems = models.ManyToManyField(Problem, verbose_name=_('problems'), through='ContestProblem')
+ problems = models.ManyToManyField(
+ Problem, verbose_name=_('problems'), through='ContestProblem'
+ )
start_time = models.DateTimeField(verbose_name=_('start time'), db_index=True)
end_time = models.DateTimeField(verbose_name=_('end time'), db_index=True)
- time_limit = models.DurationField(verbose_name=_('time limit'), blank=True, null=True)
- is_visible = models.BooleanField(verbose_name=_('publicly visible'), default=False,
- help_text=_('Should be set even for organization-private contests, where it '
- 'determines whether the contest is visible to members of the '
- 'specified organizations.'))
- is_rated = models.BooleanField(verbose_name=_('contest rated'), help_text=_('Whether this contest can be rated.'),
- default=False)
- view_contest_scoreboard = models.ManyToManyField(Profile, verbose_name=_('view contest scoreboard'), blank=True,
- related_name='view_contest_scoreboard',
- help_text=_('These users will be able to view the scoreboard.'))
- view_contest_submissions = models.ManyToManyField(Profile, verbose_name=_('can see contest submissions'),
- blank=True, related_name='view_contest_submissions',
- help_text=_('These users will be able '
- 'to see in-contest submissions.'))
- scoreboard_visibility = models.CharField(verbose_name=_('scoreboard visibility'), default=SCOREBOARD_VISIBLE,
- help_text=_('Scoreboard visibility through the duration of the contest.'),
- max_length=1, choices=SCOREBOARD_VISIBILITY)
- use_clarifications = models.BooleanField(verbose_name=_('no comments'),
- help_text=_('Use clarification system instead of comments.'),
- default=True)
- rating_floor = models.IntegerField(verbose_name=_('rating floor'),
- help_text=_('Do not rate users who have a lower rating.'), null=True, blank=True)
- rating_ceiling = models.IntegerField(verbose_name=_('rating ceiling'),
- help_text=_('Do not rate users who have a higher rating.'),
- null=True, blank=True)
- rate_all = models.BooleanField(verbose_name=_('rate all'), help_text=_('Rate all users who joined.'), default=False)
- rate_exclude = models.ManyToManyField(Profile, verbose_name=_('exclude from ratings'), blank=True,
- related_name='rate_exclude+')
- is_private = models.BooleanField(verbose_name=_('private to specific users'), default=False)
- private_contestants = models.ManyToManyField(Profile, blank=True, verbose_name=_('private contestants'),
- help_text=_('If non-empty, only these users may see the contest.'),
- related_name='private_contestants+')
- hide_problem_tags = models.BooleanField(verbose_name=_('hide problem tags'),
- help_text=_('Whether problem tags should be hidden by default.'),
- default=False)
- hide_problem_authors = models.BooleanField(verbose_name=_('hide problem authors'),
- help_text=_('Whether problem authors should be hidden by default.'),
- default=False)
- run_pretests_only = models.BooleanField(verbose_name=_('run pretests only'),
- help_text=_('Whether judges should grade pretests only, versus all '
- 'testcases. Commonly set during a contest, then unset '
- 'prior to rejudging user submissions when the contest ends.'),
- default=False)
- show_short_display = models.BooleanField(verbose_name=_('show short form settings display'),
- help_text=_('Whether to show a section containing contest settings '
- 'on the contest page or not.'),
- default=False)
- is_organization_private = models.BooleanField(verbose_name=_('private to organizations'), default=False)
- organizations = models.ManyToManyField(Organization, blank=True, verbose_name=_('organizations'),
- help_text=_('If non-empty, only these organizations may see the contest.'))
- limit_join_organizations = models.BooleanField(verbose_name=_('limit organizations that can join'), default=False)
- join_organizations = models.ManyToManyField(Organization, blank=True, verbose_name=_('join organizations'),
- help_text=_('If non-empty, only these organizations may join '
- 'the contest.'), related_name='join_only_contests')
- classes = models.ManyToManyField(Class, blank=True, verbose_name=_('classes'),
- help_text=_('If organization private, only these classes may see the contest.'))
- og_image = models.CharField(verbose_name=_('OpenGraph image'), default='', max_length=150, blank=True)
- logo_override_image = models.CharField(verbose_name=_('logo override image'), default='', max_length=150,
- blank=True,
- help_text=_('This image will replace the default site logo for users '
- 'inside the contest.'))
- tags = models.ManyToManyField(ContestTag, verbose_name=_('contest tags'), blank=True, related_name='contests')
- user_count = models.IntegerField(verbose_name=_('the amount of live participants'), default=0)
- summary = models.TextField(blank=True, verbose_name=_('contest summary'),
- help_text=_('Plain-text, shown in meta description tag, e.g. for social media.'))
- access_code = models.CharField(verbose_name=_('access code'), blank=True, default='', max_length=255,
- help_text=_('An optional code to prompt contestants before they are allowed '
- 'to join the contest. Leave it blank to disable.'))
- banned_users = models.ManyToManyField(Profile, verbose_name=_('personae non gratae'), blank=True,
- help_text=_('Bans the selected users from joining this contest.'))
- format_name = models.CharField(verbose_name=_('contest format'), default='default', max_length=32,
- choices=contest_format.choices(), help_text=_('The contest format module to use.'))
- format_config = JSONField(verbose_name=_('contest format configuration'), null=True, blank=True,
- help_text=_('A JSON object to serve as the configuration for the chosen contest format '
- 'module. Leave empty to use None. Exact format depends on the contest format '
- 'selected.'))
- problem_label_script = models.TextField(verbose_name=_('contest problem label script'), blank=True,
- help_text=_('A custom Lua function to generate problem labels. Requires a '
- 'single function with an integer parameter, the zero-indexed '
- 'contest problem index, and returns a string, the label.'))
- locked_after = models.DateTimeField(verbose_name=_('contest lock'), null=True, blank=True,
- help_text=_('Prevent submissions from this contest '
- 'from being rejudged after this date.'))
- points_precision = models.IntegerField(verbose_name=_('precision points'), default=3,
- validators=[MinValueValidator(0), MaxValueValidator(10)],
- help_text=_('Number of digits to round points to.'))
+ time_limit = models.DurationField(
+ verbose_name=_('time limit'), blank=True, null=True
+ )
+ is_visible = models.BooleanField(
+ verbose_name=_('publicly visible'),
+ default=False,
+ help_text=_(
+ 'Should be set even for organization-private contests, where it '
+ 'determines whether the contest is visible to members of the '
+ 'specified organizations.'
+ ),
+ )
+ is_rated = models.BooleanField(
+ verbose_name=_('contest rated'),
+ help_text=_('Whether this contest can be rated.'),
+ default=False,
+ )
+ view_contest_scoreboard = models.ManyToManyField(
+ Profile,
+ verbose_name=_('view contest scoreboard'),
+ blank=True,
+ related_name='view_contest_scoreboard',
+ help_text=_('These users will be able to view the scoreboard.'),
+ )
+ view_contest_submissions = models.ManyToManyField(
+ Profile,
+ verbose_name=_('can see contest submissions'),
+ blank=True,
+ related_name='view_contest_submissions',
+ help_text=_('These users will be able ' 'to see in-contest submissions.'),
+ )
+ scoreboard_visibility = models.CharField(
+ verbose_name=_('scoreboard visibility'),
+ default=SCOREBOARD_VISIBLE,
+ help_text=_('Scoreboard visibility through the duration of the contest.'),
+ max_length=1,
+ choices=SCOREBOARD_VISIBILITY,
+ )
+ use_clarifications = models.BooleanField(
+ verbose_name=_('no comments'),
+ help_text=_('Use clarification system instead of comments.'),
+ default=True,
+ )
+ rating_floor = models.IntegerField(
+ verbose_name=_('rating floor'),
+ help_text=_('Do not rate users who have a lower rating.'),
+ null=True,
+ blank=True,
+ )
+ rating_ceiling = models.IntegerField(
+ verbose_name=_('rating ceiling'),
+ help_text=_('Do not rate users who have a higher rating.'),
+ null=True,
+ blank=True,
+ )
+ rate_all = models.BooleanField(
+ verbose_name=_('rate all'),
+ help_text=_('Rate all users who joined.'),
+ default=False,
+ )
+ rate_exclude = models.ManyToManyField(
+ Profile,
+ verbose_name=_('exclude from ratings'),
+ blank=True,
+ related_name='rate_exclude+',
+ )
+ is_private = models.BooleanField(
+ verbose_name=_('private to specific users'), default=False
+ )
+ private_contestants = models.ManyToManyField(
+ Profile,
+ blank=True,
+ verbose_name=_('private contestants'),
+ help_text=_('If non-empty, only these users may see the contest.'),
+ related_name='private_contestants+',
+ )
+ hide_problem_tags = models.BooleanField(
+ verbose_name=_('hide problem tags'),
+ help_text=_('Whether problem tags should be hidden by default.'),
+ default=False,
+ )
+ hide_problem_authors = models.BooleanField(
+ verbose_name=_('hide problem authors'),
+ help_text=_('Whether problem authors should be hidden by default.'),
+ default=False,
+ )
+ run_pretests_only = models.BooleanField(
+ verbose_name=_('run pretests only'),
+ help_text=_(
+ 'Whether judges should grade pretests only, versus all '
+ 'testcases. Commonly set during a contest, then unset '
+ 'prior to rejudging user submissions when the contest ends.'
+ ),
+ default=False,
+ )
+ show_short_display = models.BooleanField(
+ verbose_name=_('show short form settings display'),
+ help_text=_(
+ 'Whether to show a section containing contest settings '
+ 'on the contest page or not.'
+ ),
+ default=False,
+ )
+ is_organization_private = models.BooleanField(
+ verbose_name=_('private to organizations'), default=False
+ )
+ organizations = models.ManyToManyField(
+ Organization,
+ blank=True,
+ verbose_name=_('organizations'),
+ help_text=_('If non-empty, only these organizations may see the contest.'),
+ )
+ limit_join_organizations = models.BooleanField(
+ verbose_name=_('limit organizations that can join'), default=False
+ )
+ join_organizations = models.ManyToManyField(
+ Organization,
+ blank=True,
+ verbose_name=_('join organizations'),
+ help_text=_('If non-empty, only these organizations may join ' 'the contest.'),
+ related_name='join_only_contests',
+ )
+ classes = models.ManyToManyField(
+ Class,
+ blank=True,
+ verbose_name=_('classes'),
+ help_text=_('If organization private, only these classes may see the contest.'),
+ )
+ og_image = models.CharField(
+ verbose_name=_('OpenGraph image'), default='', max_length=150, blank=True
+ )
+ logo_override_image = models.CharField(
+ verbose_name=_('logo override image'),
+ default='',
+ max_length=150,
+ blank=True,
+ help_text=_(
+ 'This image will replace the default site logo for users '
+ 'inside the contest.'
+ ),
+ )
+ tags = models.ManyToManyField(
+ ContestTag, verbose_name=_('contest tags'), blank=True, related_name='contests'
+ )
+ user_count = models.IntegerField(
+ verbose_name=_('the amount of live participants'), default=0
+ )
+ summary = models.TextField(
+ blank=True,
+ verbose_name=_('contest summary'),
+ help_text=_(
+ 'Plain-text, shown in meta description tag, e.g. for social media.'
+ ),
+ )
+ access_code = models.CharField(
+ verbose_name=_('access code'),
+ blank=True,
+ default='',
+ max_length=255,
+ help_text=_(
+ 'An optional code to prompt contestants before they are allowed '
+ 'to join the contest. Leave it blank to disable.'
+ ),
+ )
+ banned_users = models.ManyToManyField(
+ Profile,
+ verbose_name=_('personae non gratae'),
+ blank=True,
+ help_text=_('Bans the selected users from joining this contest.'),
+ )
+ format_name = models.CharField(
+ verbose_name=_('contest format'),
+ default='default',
+ max_length=32,
+ choices=contest_format.choices(),
+ help_text=_('The contest format module to use.'),
+ )
+ format_config = JSONField(
+ verbose_name=_('contest format configuration'),
+ null=True,
+ blank=True,
+ help_text=_(
+ 'A JSON object to serve as the configuration for the chosen contest format '
+ 'module. Leave empty to use None. Exact format depends on the contest format '
+ 'selected.'
+ ),
+ )
+ problem_label_script = models.TextField(
+ verbose_name=_('contest problem label script'),
+ blank=True,
+ help_text=_(
+ 'A custom Lua function to generate problem labels. Requires a '
+ 'single function with an integer parameter, the zero-indexed '
+ 'contest problem index, and returns a string, the label.'
+ ),
+ )
+ locked_after = models.DateTimeField(
+ verbose_name=_('contest lock'),
+ null=True,
+ blank=True,
+ help_text=_(
+ 'Prevent submissions from this contest '
+ 'from being rejudged after this date.'
+ ),
+ )
+ points_precision = models.IntegerField(
+ verbose_name=_('precision points'),
+ default=3,
+ validators=[MinValueValidator(0), MaxValueValidator(10)],
+ help_text=_('Number of digits to round points to.'),
+ )
@cached_property
def format_class(self):
@@ -191,13 +368,18 @@ def get_label_for_problem(self):
def DENY_ALL(obj, attr_name, is_setting):
raise AttributeError()
- lua = LuaRuntime(attribute_filter=DENY_ALL, register_eval=False, register_builtins=False)
+
+ lua = LuaRuntime(
+ attribute_filter=DENY_ALL, register_eval=False, register_builtins=False
+ )
return lua.eval(self.problem_label_script)
def clean(self):
# Django will complain if you didn't fill in start_time or end_time, so we don't have to.
if self.start_time and self.end_time and self.start_time >= self.end_time:
- raise ValidationError('What is this? A contest that ended before it starts?')
+ raise ValidationError(
+ 'What is this? A contest that ended before it starts?'
+ )
self.format_class.validate(self.format_config)
try:
@@ -208,12 +390,18 @@ def clean(self):
raise ValidationError('Contest problem label script: %s' % e)
else:
if not isinstance(label, str):
- raise ValidationError('Contest problem label script: script should return a string.')
+ raise ValidationError(
+ 'Contest problem label script: script should return a string.'
+ )
def is_in_contest(self, user):
if user.is_authenticated:
profile = user.profile
- return profile and profile.current_contest is not None and profile.current_contest.contest == self
+ return (
+ profile
+ and profile.current_contest is not None
+ and profile.current_contest.contest == self
+ )
return False
def can_see_own_scoreboard(self, user):
@@ -221,7 +409,11 @@ def can_see_own_scoreboard(self, user):
return True
if not self.started:
return False
- if not self.show_scoreboard and not self.is_in_contest(user) and not self.has_completed_contest(user):
+ if (
+ not self.show_scoreboard
+ and not self.is_in_contest(user)
+ and not self.has_completed_contest(user)
+ ):
return False
return True
@@ -230,7 +422,9 @@ def can_see_full_scoreboard(self, user):
return True
if not user.is_authenticated:
return False
- if user.has_perm('judge.see_private_contest') or user.has_perm('judge.edit_all_contest'):
+ if user.has_perm('judge.see_private_contest') or user.has_perm(
+ 'judge.edit_all_contest'
+ ):
return True
if user.profile.id in self.editor_ids:
return True
@@ -240,13 +434,18 @@ def can_see_full_scoreboard(self, user):
return True
if self.view_contest_scoreboard.filter(id=user.profile.id).exists():
return True
- if self.scoreboard_visibility == self.SCOREBOARD_AFTER_PARTICIPATION and self.has_completed_contest(user):
+ if (
+ self.scoreboard_visibility == self.SCOREBOARD_AFTER_PARTICIPATION
+ and self.has_completed_contest(user)
+ ):
return True
return False
def has_completed_contest(self, user):
if user.is_authenticated:
- participation = self.users.filter(virtual=ContestParticipation.LIVE, user=user.profile).first()
+ participation = self.users.filter(
+ virtual=ContestParticipation.LIVE, user=user.profile
+ ).first()
if participation and participation.ended:
return True
return False
@@ -255,8 +454,11 @@ def has_completed_contest(self, user):
def show_scoreboard(self):
if not self.started:
return False
- if (self.scoreboard_visibility in (self.SCOREBOARD_AFTER_CONTEST, self.SCOREBOARD_AFTER_PARTICIPATION) and
- not self.ended):
+ if (
+ self.scoreboard_visibility
+ in (self.SCOREBOARD_AFTER_CONTEST, self.SCOREBOARD_AFTER_PARTICIPATION)
+ and not self.ended
+ ):
return False
return self.scoreboard_visibility != self.SCOREBOARD_HIDDEN
@@ -293,20 +495,29 @@ def ended(self):
@cached_property
def author_ids(self):
- return Contest.authors.through.objects.filter(contest=self).values_list('profile_id', flat=True)
+ return Contest.authors.through.objects.filter(contest=self).values_list(
+ 'profile_id', flat=True
+ )
@cached_property
def editor_ids(self):
return self.author_ids.union(
- Contest.curators.through.objects.filter(contest=self).values_list('profile_id', flat=True))
+ Contest.curators.through.objects.filter(contest=self).values_list(
+ 'profile_id', flat=True
+ )
+ )
@cached_property
def tester_ids(self):
- return Contest.testers.through.objects.filter(contest=self).values_list('profile_id', flat=True)
+ return Contest.testers.through.objects.filter(contest=self).values_list(
+ 'profile_id', flat=True
+ )
@cached_property
def spectator_ids(self):
- return Contest.spectators.through.objects.filter(contest=self).values_list('profile_id', flat=True)
+ return Contest.spectators.through.objects.filter(contest=self).values_list(
+ 'profile_id', flat=True
+ )
def __str__(self):
return self.name
@@ -337,7 +548,9 @@ def access_check(self, user):
return
# If the user can view or edit all contests
- if user.has_perm('judge.see_private_contest') or user.has_perm('judge.edit_all_contest'):
+ if user.has_perm('judge.see_private_contest') or user.has_perm(
+ 'judge.edit_all_contest'
+ ):
return
# User is organizer or curator for contest
@@ -363,8 +576,10 @@ def access_check(self, user):
if self.view_contest_scoreboard.filter(id=user.profile.id).exists():
return
- in_org = (self.organizations.filter(id__in=user.profile.organizations.all()).exists() or
- self.classes.filter(id__in=user.profile.classes.all()).exists())
+ in_org = (
+ self.organizations.filter(id__in=user.profile.organizations.all()).exists()
+ or self.classes.filter(id__in=user.profile.classes.all()).exists()
+ )
in_users = self.private_contestants.filter(id=user.profile.id).exists()
if not self.is_private and self.is_organization_private:
@@ -393,7 +608,9 @@ def is_live_joinable_by(self, user):
# Can be populated using annotate for performance in list views.
editor_or_tester = getattr(self, 'editor_or_tester', None)
if editor_or_tester is None:
- editor_or_tester = user.profile.id in self.editor_ids or user.profile.id in self.tester_ids
+ editor_or_tester = (
+ user.profile.id in self.editor_ids or user.profile.id in self.tester_ids
+ )
if editor_or_tester:
return False
@@ -406,7 +623,9 @@ def is_live_joinable_by(self, user):
return False
if self.limit_join_organizations:
- return self.join_organizations.filter(id__in=user.profile.organizations.all()).exists()
+ return self.join_organizations.filter(
+ id__in=user.profile.organizations.all()
+ ).exists()
return True
# Also skips access check
@@ -418,7 +637,9 @@ def is_spectatable_by(self, user):
return True
if self.limit_join_organizations:
- return self.join_organizations.filter(id__in=user.profile.organizations.all()).exists()
+ return self.join_organizations.filter(
+ id__in=user.profile.organizations.all()
+ ).exists()
return True
def is_accessible_by(self, user):
@@ -435,7 +656,10 @@ def is_editable_by(self, user):
return True
# If the user is a contest organizer or curator
- if user.has_perm('judge.edit_own_contest') and user.profile.id in self.editor_ids:
+ if (
+ user.has_perm('judge.edit_own_contest')
+ and user.profile.id in self.editor_ids
+ ):
return True
return False
@@ -443,20 +667,40 @@ def is_editable_by(self, user):
@classmethod
def get_visible_contests(cls, user):
if not user.is_authenticated:
- return cls.objects.filter(is_visible=True, is_organization_private=False, is_private=False) \
- .defer('description').distinct()
+ return (
+ cls.objects.filter(
+ is_visible=True, is_organization_private=False, is_private=False
+ )
+ .defer('description')
+ .distinct()
+ )
queryset = cls.objects.defer('description')
- if not (user.has_perm('judge.see_private_contest') or user.has_perm('judge.edit_all_contest')):
- org_check = (Q(organizations__in=user.profile.organizations.all()) |
- Q(classes__in=user.profile.classes.all()))
+ if not (
+ user.has_perm('judge.see_private_contest')
+ or user.has_perm('judge.edit_all_contest')
+ ):
+ org_check = Q(organizations__in=user.profile.organizations.all()) | Q(
+ classes__in=user.profile.classes.all()
+ )
q = Q(is_visible=True)
q &= (
- Q(view_contest_scoreboard=user.profile) |
- Q(is_organization_private=False, is_private=False) |
- Q(is_organization_private=False, is_private=True, private_contestants=user.profile) |
- (Q(is_organization_private=True, is_private=False) & org_check) |
- (Q(is_organization_private=True, is_private=True, private_contestants=user.profile) & org_check)
+ Q(view_contest_scoreboard=user.profile)
+ | Q(is_organization_private=False, is_private=False)
+ | Q(
+ is_organization_private=False,
+ is_private=True,
+ private_contestants=user.profile,
+ )
+ | (Q(is_organization_private=True, is_private=False) & org_check)
+ | (
+ Q(
+ is_organization_private=True,
+ is_private=True,
+ private_contestants=user.profile,
+ )
+ & org_check
+ )
)
q |= Q(authors=user.profile)
@@ -468,9 +712,12 @@ def get_visible_contests(cls, user):
def rate(self):
with transaction.atomic():
- Rating.objects.filter(contest__end_time__range=(self.end_time, self._now)).delete()
+ Rating.objects.filter(
+ contest__end_time__range=(self.end_time, self._now)
+ ).delete()
for contest in Contest.objects.filter(
- is_rated=True, end_time__range=(self.end_time, self._now),
+ is_rated=True,
+ end_time__range=(self.end_time, self._now),
).order_by('end_time'):
rate_contest(contest)
@@ -496,17 +743,37 @@ class ContestParticipation(models.Model):
LIVE = 0
SPECTATE = -1
- contest = models.ForeignKey(Contest, verbose_name=_('associated contest'), related_name='users', on_delete=CASCADE)
- user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='contest_history', on_delete=CASCADE)
- real_start = models.DateTimeField(verbose_name=_('start time'), default=timezone.now, db_column='start')
+ contest = models.ForeignKey(
+ Contest,
+ verbose_name=_('associated contest'),
+ related_name='users',
+ on_delete=CASCADE,
+ )
+ user = models.ForeignKey(
+ Profile,
+ verbose_name=_('user'),
+ related_name='contest_history',
+ on_delete=CASCADE,
+ )
+ real_start = models.DateTimeField(
+ verbose_name=_('start time'), default=timezone.now, db_column='start'
+ )
score = models.FloatField(verbose_name=_('score'), default=0, db_index=True)
cumtime = models.PositiveIntegerField(verbose_name=_('cumulative time'), default=0)
- is_disqualified = models.BooleanField(verbose_name=_('is disqualified'), default=False,
- help_text=_('Whether this participation is disqualified.'))
+ is_disqualified = models.BooleanField(
+ verbose_name=_('is disqualified'),
+ default=False,
+ help_text=_('Whether this participation is disqualified.'),
+ )
tiebreaker = models.FloatField(verbose_name=_('tie-breaking field'), default=0.0)
- virtual = models.IntegerField(verbose_name=_('virtual participation id'), default=LIVE,
- help_text=_('0 means non-virtual, otherwise the n-th virtual participation.'))
- format_data = JSONField(verbose_name=_('contest format specific data'), null=True, blank=True)
+ virtual = models.IntegerField(
+ verbose_name=_('virtual participation id'),
+ default=LIVE,
+ help_text=_('0 means non-virtual, otherwise the n-th virtual participation.'),
+ )
+ format_data = JSONField(
+ verbose_name=_('contest format specific data'), null=True, blank=True
+ )
def recompute_results(self):
with transaction.atomic():
@@ -516,6 +783,7 @@ def recompute_results(self):
self.cumtime = 0
self.tiebreaker = 0
self.save(update_fields=['score', 'cumtime', 'tiebreaker'])
+
recompute_results.alters_data = True
def set_disqualified(self, disqualified):
@@ -529,6 +797,7 @@ def set_disqualified(self, disqualified):
self.contest.banned_users.add(self.user)
else:
self.contest.banned_users.remove(self.user)
+
set_disqualified.alters_data = True
@property
@@ -542,7 +811,11 @@ def spectate(self):
@cached_property
def start(self):
contest = self.contest
- return contest.start_time if contest.time_limit is None and (self.live or self.spectate) else self.real_start
+ return (
+ contest.start_time
+ if contest.time_limit is None and (self.live or self.spectate)
+ else self.real_start
+ )
@cached_property
def end_time(self):
@@ -554,8 +827,11 @@ def end_time(self):
return self.real_start + contest.time_limit
else:
return self.real_start + (contest.end_time - contest.start_time)
- return contest.end_time if contest.time_limit is None else \
- min(self.real_start + contest.time_limit, contest.end_time)
+ return (
+ contest.end_time
+ if contest.time_limit is None
+ else min(self.real_start + contest.time_limit, contest.end_time)
+ )
@cached_property
def _now(self):
@@ -574,12 +850,20 @@ def time_remaining(self):
def __str__(self):
if self.spectate:
- return _('%(user)s spectating in %(contest)s') % {'user': self.user.username, 'contest': self.contest.name}
+ return _('%(user)s spectating in %(contest)s') % {
+ 'user': self.user.username,
+ 'contest': self.contest.name,
+ }
if self.virtual:
return _('%(user)s in %(contest)s, v%(id)d') % {
- 'user': self.user.username, 'contest': self.contest.name, 'id': self.virtual,
+ 'user': self.user.username,
+ 'contest': self.contest.name,
+ 'id': self.virtual,
}
- return _('%(user)s in %(contest)s') % {'user': self.user.username, 'contest': self.contest.name}
+ return _('%(user)s in %(contest)s') % {
+ 'user': self.user.username,
+ 'contest': self.contest.name,
+ }
class Meta:
verbose_name = _('contest participation')
@@ -589,20 +873,40 @@ class Meta:
class ContestProblem(models.Model):
- problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='contests', on_delete=CASCADE)
- contest = models.ForeignKey(Contest, verbose_name=_('contest'), related_name='contest_problems', on_delete=CASCADE)
+ problem = models.ForeignKey(
+ Problem, verbose_name=_('problem'), related_name='contests', on_delete=CASCADE
+ )
+ contest = models.ForeignKey(
+ Contest,
+ verbose_name=_('contest'),
+ related_name='contest_problems',
+ on_delete=CASCADE,
+ )
points = models.IntegerField(verbose_name=_('points'))
partial = models.BooleanField(default=True, verbose_name=_('partial'))
is_pretested = models.BooleanField(default=False, verbose_name=_('is pretested'))
order = models.PositiveIntegerField(db_index=True, verbose_name=_('order'))
- output_prefix_override = models.IntegerField(verbose_name=_('output prefix length override'),
- default=0, null=True, blank=True)
- max_submissions = models.IntegerField(verbose_name=_('max submissions'),
- help_text=_('Maximum number of submissions for this problem, '
- 'or leave blank for no limit.'),
- default=None, null=True, blank=True,
- validators=[MinValueOrNoneValidator(1, _('Why include a problem you '
- "can't submit to?"))])
+ output_prefix_override = models.IntegerField(
+ verbose_name=_('output prefix length override'),
+ default=0,
+ null=True,
+ blank=True,
+ )
+ max_submissions = models.IntegerField(
+ verbose_name=_('max submissions'),
+ help_text=_(
+ 'Maximum number of submissions for this problem, '
+ 'or leave blank for no limit.'
+ ),
+ default=None,
+ null=True,
+ blank=True,
+ validators=[
+ MinValueOrNoneValidator(
+ 1, _('Why include a problem you ' "can't submit to?")
+ )
+ ],
+ )
class Meta:
unique_together = ('problem', 'contest')
@@ -612,16 +916,32 @@ class Meta:
class ContestSubmission(models.Model):
- submission = models.OneToOneField(Submission, verbose_name=_('submission'),
- related_name='contest', on_delete=CASCADE)
- problem = models.ForeignKey(ContestProblem, verbose_name=_('problem'), on_delete=CASCADE,
- related_name='submissions', related_query_name='submission')
- participation = models.ForeignKey(ContestParticipation, verbose_name=_('participation'), on_delete=CASCADE,
- related_name='submissions', related_query_name='submission')
+ submission = models.OneToOneField(
+ Submission,
+ verbose_name=_('submission'),
+ related_name='contest',
+ on_delete=CASCADE,
+ )
+ problem = models.ForeignKey(
+ ContestProblem,
+ verbose_name=_('problem'),
+ on_delete=CASCADE,
+ related_name='submissions',
+ related_query_name='submission',
+ )
+ participation = models.ForeignKey(
+ ContestParticipation,
+ verbose_name=_('participation'),
+ on_delete=CASCADE,
+ related_name='submissions',
+ related_query_name='submission',
+ )
points = models.FloatField(default=0.0, verbose_name=_('points'))
- is_pretest = models.BooleanField(verbose_name=_('is pretested'),
- help_text=_('Whether this submission was ran only on pretests.'),
- default=False)
+ is_pretest = models.BooleanField(
+ verbose_name=_('is pretested'),
+ help_text=_('Whether this submission was ran only on pretests.'),
+ default=False,
+ )
class Meta:
verbose_name = _('contest submission')
@@ -629,10 +949,18 @@ class Meta:
class Rating(models.Model):
- user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='ratings', on_delete=CASCADE)
- contest = models.ForeignKey(Contest, verbose_name=_('contest'), related_name='ratings', on_delete=CASCADE)
- participation = models.OneToOneField(ContestParticipation, verbose_name=_('participation'),
- related_name='rating', on_delete=CASCADE)
+ user = models.ForeignKey(
+ Profile, verbose_name=_('user'), related_name='ratings', on_delete=CASCADE
+ )
+ contest = models.ForeignKey(
+ Contest, verbose_name=_('contest'), related_name='ratings', on_delete=CASCADE
+ )
+ participation = models.OneToOneField(
+ ContestParticipation,
+ verbose_name=_('participation'),
+ related_name='rating',
+ on_delete=CASCADE,
+ )
rank = models.IntegerField(verbose_name=_('rank'))
rating = models.IntegerField(verbose_name=_('rating'))
mean = models.FloatField(verbose_name=_('raw rating'))
@@ -653,8 +981,12 @@ class ContestMoss(models.Model):
('Python', MOSS_LANG_PYTHON),
]
- contest = models.ForeignKey(Contest, verbose_name=_('contest'), related_name='moss', on_delete=CASCADE)
- problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='moss', on_delete=CASCADE)
+ contest = models.ForeignKey(
+ Contest, verbose_name=_('contest'), related_name='moss', on_delete=CASCADE
+ )
+ problem = models.ForeignKey(
+ Problem, verbose_name=_('problem'), related_name='moss', on_delete=CASCADE
+ )
language = models.CharField(max_length=10)
submission_count = models.PositiveIntegerField(default=0)
url = models.URLField(null=True, blank=True)
diff --git a/judge/models/interface.py b/judge/models/interface.py
index 565900744a..f683c036ca 100644
--- a/judge/models/interface.py
+++ b/judge/models/interface.py
@@ -44,9 +44,17 @@ class MPTTMeta:
key = models.CharField(max_length=10, unique=True, verbose_name=_('identifier'))
label = models.CharField(max_length=20, verbose_name=_('label'))
path = models.CharField(max_length=255, verbose_name=_('link path'))
- regex = models.TextField(verbose_name=_('highlight regex'), validators=[validate_regex])
- parent = TreeForeignKey('self', verbose_name=_('parent item'), null=True, blank=True,
- related_name='children', on_delete=models.CASCADE)
+ regex = models.TextField(
+ verbose_name=_('highlight regex'), validators=[validate_regex]
+ )
+ parent = TreeForeignKey(
+ 'self',
+ verbose_name=_('parent item'),
+ null=True,
+ blank=True,
+ related_name='children',
+ on_delete=models.CASCADE,
+ )
def __str__(self):
return self.label
@@ -71,7 +79,9 @@ class BlogPost(models.Model):
publish_on = models.DateTimeField(verbose_name=_('publish after'))
content = models.TextField(verbose_name=_('post content'))
summary = models.TextField(verbose_name=_('post summary'), blank=True)
- og_image = models.CharField(verbose_name=_('OpenGraph image'), default='', max_length=150, blank=True)
+ og_image = models.CharField(
+ verbose_name=_('OpenGraph image'), default='', max_length=150, blank=True
+ )
def __str__(self):
return self.title
@@ -89,7 +99,10 @@ def is_editable_by(self, user):
return False
if user.has_perm('judge.edit_all_post'):
return True
- return user.has_perm('judge.change_blogpost') and self.authors.filter(id=user.profile.id).exists()
+ return (
+ user.has_perm('judge.change_blogpost')
+ and self.authors.filter(id=user.profile.id).exists()
+ )
class Meta:
permissions = (
diff --git a/judge/models/problem.py b/judge/models/problem.py
index aa436724fb..792ce4dbc5 100644
--- a/judge/models/problem.py
+++ b/judge/models/problem.py
@@ -19,20 +19,37 @@
from judge.models.runtime import Language
from judge.user_translations import gettext as user_gettext
-__all__ = ['ProblemGroup', 'ProblemType', 'Problem', 'ProblemTranslation', 'ProblemClarification', 'License',
- 'Solution', 'SubmissionSourceAccess', 'TranslatedProblemQuerySet']
+__all__ = [
+ 'ProblemGroup',
+ 'ProblemType',
+ 'Problem',
+ 'ProblemTranslation',
+ 'ProblemClarification',
+ 'License',
+ 'Solution',
+ 'SubmissionSourceAccess',
+ 'TranslatedProblemQuerySet',
+]
def disallowed_characters_validator(text):
- common_disallowed_characters = set(text) & settings.DMOJ_PROBLEM_STATEMENT_DISALLOWED_CHARACTERS
+ common_disallowed_characters = (
+ set(text) & settings.DMOJ_PROBLEM_STATEMENT_DISALLOWED_CHARACTERS
+ )
if common_disallowed_characters:
- raise ValidationError(_('Disallowed characters: %(value)s'),
- params={'value': ''.join(common_disallowed_characters)})
+ raise ValidationError(
+ _('Disallowed characters: %(value)s'),
+ params={'value': ''.join(common_disallowed_characters)},
+ )
class ProblemType(models.Model):
- name = models.CharField(max_length=20, verbose_name=_('problem category ID'), unique=True)
- full_name = models.CharField(max_length=100, verbose_name=_('problem category name'))
+ name = models.CharField(
+ max_length=20, verbose_name=_('problem category ID'), unique=True
+ )
+ full_name = models.CharField(
+ max_length=100, verbose_name=_('problem category name')
+ )
def __str__(self):
return self.full_name
@@ -44,7 +61,9 @@ class Meta:
class ProblemGroup(models.Model):
- name = models.CharField(max_length=20, verbose_name=_('problem group ID'), unique=True)
+ name = models.CharField(
+ max_length=20, verbose_name=_('problem group ID'), unique=True
+ )
full_name = models.CharField(max_length=100, verbose_name=_('problem group name'))
def __str__(self):
@@ -57,13 +76,26 @@ class Meta:
class License(models.Model):
- key = models.CharField(max_length=20, unique=True, verbose_name=_('key'),
- validators=[RegexValidator(r'^[-\w.]+$', r'License key must be ^[-\w.]+$')])
+ key = models.CharField(
+ max_length=20,
+ unique=True,
+ verbose_name=_('key'),
+ validators=[RegexValidator(r'^[-\w.]+$', r'License key must be ^[-\w.]+$')],
+ )
link = models.CharField(max_length=256, verbose_name=_('link'))
name = models.CharField(max_length=256, verbose_name=_('full name'))
- display = models.CharField(max_length=256, blank=True, verbose_name=_('short name'),
- help_text=_('Displayed on pages under this license.'))
- icon = models.CharField(max_length=256, blank=True, verbose_name=_('icon'), help_text=_('URL to the icon.'))
+ display = models.CharField(
+ max_length=256,
+ blank=True,
+ verbose_name=_('short name'),
+ help_text=_('Displayed on pages under this license.'),
+ )
+ icon = models.CharField(
+ max_length=256,
+ blank=True,
+ verbose_name=_('icon'),
+ help_text=_('URL to the icon.'),
+ )
text = models.TextField(verbose_name=_('license text'))
def __str__(self):
@@ -79,12 +111,21 @@ class Meta:
class TranslatedProblemQuerySet(SearchQuerySet):
def __init__(self, **kwargs):
- super(TranslatedProblemQuerySet, self).__init__(('code', 'name', 'description'), **kwargs)
+ super(TranslatedProblemQuerySet, self).__init__(
+ ('code', 'name', 'description'), **kwargs
+ )
def add_i18n_name(self, language):
- return self.annotate(i18n_translation=FilteredRelation(
- 'translations', condition=Q(translations__language=language),
- )).annotate(i18n_name=Coalesce(F('i18n_translation__name'), F('name'), output_field=models.CharField()))
+ return self.annotate(
+ i18n_translation=FilteredRelation(
+ 'translations',
+ condition=Q(translations__language=language),
+ )
+ ).annotate(
+ i18n_name=Coalesce(
+ F('i18n_translation__name'), F('name'), output_field=models.CharField()
+ )
+ )
class SubmissionSourceAccess:
@@ -114,71 +155,175 @@ class Problem(models.Model):
(SubmissionSourceAccess.ONLY_OWN, _('Only own submissions')),
)
- code = models.CharField(max_length=20, verbose_name=_('problem code'), unique=True,
- validators=[RegexValidator('^[a-z0-9]+$', _('Problem code must be ^[a-z0-9]+$'))],
- help_text=_('A short, unique code for the problem, used in the URL after /problem/'))
- name = models.CharField(max_length=100, verbose_name=_('problem name'), db_index=True,
- help_text=_('The full name of the problem, as shown in the problem list.'),
- validators=[disallowed_characters_validator])
- description = models.TextField(verbose_name=_('problem body'), validators=[disallowed_characters_validator])
- authors = models.ManyToManyField(Profile, verbose_name=_('creators'), blank=True, related_name='authored_problems',
- help_text=_('These users will be able to edit the problem, '
- 'and be listed as authors.'))
- curators = models.ManyToManyField(Profile, verbose_name=_('curators'), blank=True, related_name='curated_problems',
- help_text=_('These users will be able to edit the problem, '
- 'but not be listed as authors.'))
- testers = models.ManyToManyField(Profile, verbose_name=_('testers'), blank=True, related_name='tested_problems',
- help_text=_(
- 'These users will be able to view the private problem, but not edit it.'))
- types = models.ManyToManyField(ProblemType, verbose_name=_('problem types'),
- help_text=_("The type of problem, as shown on the problem's page."))
- group = models.ForeignKey(ProblemGroup, verbose_name=_('problem group'), on_delete=CASCADE,
- help_text=_('The group of problem, shown under Category in the problem list.'))
- time_limit = models.FloatField(verbose_name=_('time limit'),
- help_text=_('The time limit for this problem, in seconds. '
- 'Fractional seconds (e.g. 1.5) are supported.'),
- validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_TIME_LIMIT),
- MaxValueValidator(settings.DMOJ_PROBLEM_MAX_TIME_LIMIT)])
- memory_limit = models.PositiveIntegerField(verbose_name=_('memory limit'),
- help_text=_('The memory limit for this problem, in kilobytes '
- '(e.g. 256mb = 262144 kilobytes).'),
- validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_MEMORY_LIMIT),
- MaxValueValidator(settings.DMOJ_PROBLEM_MAX_MEMORY_LIMIT)])
+ code = models.CharField(
+ max_length=20,
+ verbose_name=_('problem code'),
+ unique=True,
+ validators=[
+ RegexValidator('^[a-z0-9]+$', _('Problem code must be ^[a-z0-9]+$'))
+ ],
+ help_text=_(
+ 'A short, unique code for the problem, used in the URL after /problem/'
+ ),
+ )
+ name = models.CharField(
+ max_length=100,
+ verbose_name=_('problem name'),
+ db_index=True,
+ help_text=_('The full name of the problem, as shown in the problem list.'),
+ validators=[disallowed_characters_validator],
+ )
+ description = models.TextField(
+ verbose_name=_('problem body'), validators=[disallowed_characters_validator]
+ )
+ authors = models.ManyToManyField(
+ Profile,
+ verbose_name=_('creators'),
+ blank=True,
+ related_name='authored_problems',
+ help_text=_(
+ 'These users will be able to edit the problem, ' 'and be listed as authors.'
+ ),
+ )
+ curators = models.ManyToManyField(
+ Profile,
+ verbose_name=_('curators'),
+ blank=True,
+ related_name='curated_problems',
+ help_text=_(
+ 'These users will be able to edit the problem, '
+ 'but not be listed as authors.'
+ ),
+ )
+ testers = models.ManyToManyField(
+ Profile,
+ verbose_name=_('testers'),
+ blank=True,
+ related_name='tested_problems',
+ help_text=_(
+ 'These users will be able to view the private problem, but not edit it.'
+ ),
+ )
+ types = models.ManyToManyField(
+ ProblemType,
+ verbose_name=_('problem types'),
+ help_text=_("The type of problem, as shown on the problem's page."),
+ )
+ group = models.ForeignKey(
+ ProblemGroup,
+ verbose_name=_('problem group'),
+ on_delete=CASCADE,
+ help_text=_('The group of problem, shown under Category in the problem list.'),
+ )
+ time_limit = models.FloatField(
+ verbose_name=_('time limit'),
+ help_text=_(
+ 'The time limit for this problem, in seconds. '
+ 'Fractional seconds (e.g. 1.5) are supported.'
+ ),
+ validators=[
+ MinValueValidator(settings.DMOJ_PROBLEM_MIN_TIME_LIMIT),
+ MaxValueValidator(settings.DMOJ_PROBLEM_MAX_TIME_LIMIT),
+ ],
+ )
+ memory_limit = models.PositiveIntegerField(
+ verbose_name=_('memory limit'),
+ help_text=_(
+ 'The memory limit for this problem, in kilobytes '
+ '(e.g. 256mb = 262144 kilobytes).'
+ ),
+ validators=[
+ MinValueValidator(settings.DMOJ_PROBLEM_MIN_MEMORY_LIMIT),
+ MaxValueValidator(settings.DMOJ_PROBLEM_MAX_MEMORY_LIMIT),
+ ],
+ )
short_circuit = models.BooleanField(verbose_name=_('short circuit'), default=False)
- points = models.FloatField(verbose_name=_('points'),
- help_text=_('Points awarded for problem completion. '
- "Points are displayed with a 'p' suffix if partial."),
- validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_PROBLEM_POINTS)])
- partial = models.BooleanField(verbose_name=_('allows partial points'), default=False)
- allowed_languages = models.ManyToManyField(Language, verbose_name=_('allowed languages'),
- help_text=_('List of allowed submission languages.'))
- is_public = models.BooleanField(verbose_name=_('publicly visible'), db_index=True, default=False)
- is_manually_managed = models.BooleanField(verbose_name=_('manually managed'), db_index=True, default=False,
- help_text=_('Whether judges should be allowed to manage data or not.'))
- date = models.DateTimeField(verbose_name=_('date of publishing'), null=True, blank=True, db_index=True,
- help_text=_(
- "Doesn't have the magic ability to auto-publish due to backward compatibility."))
- banned_users = models.ManyToManyField(Profile, verbose_name=_('personae non gratae'), blank=True,
- help_text=_('Bans the selected users from submitting to this problem.'))
- license = models.ForeignKey(License, null=True, blank=True, on_delete=SET_NULL, verbose_name=_('license'),
- help_text=_('The license under which this problem is published.'))
- og_image = models.CharField(verbose_name=_('OpenGraph image'), max_length=150, blank=True)
- summary = models.TextField(blank=True, verbose_name=_('problem summary'),
- help_text=_('Plain-text, shown in meta description tag, e.g. for social media.'))
- user_count = models.IntegerField(verbose_name=_('number of users'), default=0,
- help_text=_('The number of users who solved the problem.'))
+ points = models.FloatField(
+ verbose_name=_('points'),
+ help_text=_(
+ 'Points awarded for problem completion. '
+ "Points are displayed with a 'p' suffix if partial."
+ ),
+ validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_PROBLEM_POINTS)],
+ )
+ partial = models.BooleanField(
+ verbose_name=_('allows partial points'), default=False
+ )
+ allowed_languages = models.ManyToManyField(
+ Language,
+ verbose_name=_('allowed languages'),
+ help_text=_('List of allowed submission languages.'),
+ )
+ is_public = models.BooleanField(
+ verbose_name=_('publicly visible'), db_index=True, default=False
+ )
+ is_manually_managed = models.BooleanField(
+ verbose_name=_('manually managed'),
+ db_index=True,
+ default=False,
+ help_text=_('Whether judges should be allowed to manage data or not.'),
+ )
+ date = models.DateTimeField(
+ verbose_name=_('date of publishing'),
+ null=True,
+ blank=True,
+ db_index=True,
+ help_text=_(
+ "Doesn't have the magic ability to auto-publish due to backward compatibility."
+ ),
+ )
+ banned_users = models.ManyToManyField(
+ Profile,
+ verbose_name=_('personae non gratae'),
+ blank=True,
+ help_text=_('Bans the selected users from submitting to this problem.'),
+ )
+ license = models.ForeignKey(
+ License,
+ null=True,
+ blank=True,
+ on_delete=SET_NULL,
+ verbose_name=_('license'),
+ help_text=_('The license under which this problem is published.'),
+ )
+ og_image = models.CharField(
+ verbose_name=_('OpenGraph image'), max_length=150, blank=True
+ )
+ summary = models.TextField(
+ blank=True,
+ verbose_name=_('problem summary'),
+ help_text=_(
+ 'Plain-text, shown in meta description tag, e.g. for social media.'
+ ),
+ )
+ user_count = models.IntegerField(
+ verbose_name=_('number of users'),
+ default=0,
+ help_text=_('The number of users who solved the problem.'),
+ )
ac_rate = models.FloatField(verbose_name=_('solve rate'), default=0)
- is_full_markup = models.BooleanField(verbose_name=_('allow full markdown access'), default=False)
- submission_source_visibility_mode = models.CharField(verbose_name=_('submission source visibility'), max_length=1,
- default=SubmissionSourceAccess.FOLLOW,
- choices=SUBMISSION_SOURCE_ACCESS)
+ is_full_markup = models.BooleanField(
+ verbose_name=_('allow full markdown access'), default=False
+ )
+ submission_source_visibility_mode = models.CharField(
+ verbose_name=_('submission source visibility'),
+ max_length=1,
+ default=SubmissionSourceAccess.FOLLOW,
+ choices=SUBMISSION_SOURCE_ACCESS,
+ )
objects = TranslatedProblemQuerySet.as_manager()
tickets = GenericRelation('Ticket')
- organizations = models.ManyToManyField(Organization, blank=True, verbose_name=_('organizations'),
- help_text=_('If private, only these organizations may see the problem.'))
- is_organization_private = models.BooleanField(verbose_name=_('private to organizations'), default=False)
+ organizations = models.ManyToManyField(
+ Organization,
+ blank=True,
+ verbose_name=_('organizations'),
+ help_text=_('If private, only these organizations may see the problem.'),
+ )
+ is_organization_private = models.BooleanField(
+ verbose_name=_('private to organizations'), default=False
+ )
def __init__(self, *args, **kwargs):
super(Problem, self).__init__(*args, **kwargs)
@@ -191,21 +336,34 @@ def types_list(self):
return list(map(user_gettext, map(attrgetter('full_name'), self.types.all())))
def languages_list(self):
- return self.allowed_languages.values_list('common_name', flat=True).distinct().order_by('common_name')
+ return (
+ self.allowed_languages.values_list('common_name', flat=True)
+ .distinct()
+ .order_by('common_name')
+ )
def is_editor(self, profile):
- return (self.authors.filter(id=profile.id) | self.curators.filter(id=profile.id)).exists()
+ return (
+ self.authors.filter(id=profile.id) | self.curators.filter(id=profile.id)
+ ).exists()
def is_editable_by(self, user):
if not user.is_authenticated:
return False
if not user.has_perm('judge.edit_own_problem'):
return False
- if user.has_perm('judge.edit_all_problem') or user.has_perm('judge.edit_public_problem') and self.is_public:
+ if (
+ user.has_perm('judge.edit_all_problem')
+ or user.has_perm('judge.edit_public_problem')
+ and self.is_public
+ ):
return True
if user.profile.id in self.editor_ids:
return True
- if self.is_organization_private and self.organizations.filter(admins=user.profile).exists():
+ if (
+ self.is_organization_private
+ and self.organizations.filter(admins=user.profile).exists()
+ ):
return True
return False
@@ -216,7 +374,10 @@ def is_accessible_by(self, user, skip_contest_problem_check=False):
current = user.profile.current_contest_id
if current is not None:
from judge.models import ContestProblem
- if ContestProblem.objects.filter(problem_id=self.id, contest__users__id=current).exists():
+
+ if ContestProblem.objects.filter(
+ problem_id=self.id, contest__users__id=current
+ ).exists():
return True
# Problem is public.
@@ -230,8 +391,9 @@ def is_accessible_by(self, user, skip_contest_problem_check=False):
return True
# If the user is in the organization.
- if user.is_authenticated and \
- self.organizations.filter(id__in=user.profile.organizations.all()):
+ if user.is_authenticated and self.organizations.filter(
+ id__in=user.profile.organizations.all()
+ ):
return True
if not user.is_authenticated:
@@ -253,7 +415,11 @@ def is_accessible_by(self, user, skip_contest_problem_check=False):
return False
def is_subs_manageable_by(self, user):
- return user.is_staff and user.has_perm('judge.rejudge_submission') and self.is_editable_by(user)
+ return (
+ user.is_staff
+ and user.has_perm('judge.rejudge_submission')
+ and self.is_editable_by(user)
+ )
@classmethod
def get_visible_problems(cls, user):
@@ -273,22 +439,30 @@ def get_visible_problems(cls, user):
queryset = cls.objects.defer('description')
edit_own_problem = user.has_perm('judge.edit_own_problem')
- edit_public_problem = edit_own_problem and user.has_perm('judge.edit_public_problem')
+ edit_public_problem = edit_own_problem and user.has_perm(
+ 'judge.edit_public_problem'
+ )
edit_all_problem = edit_own_problem and user.has_perm('judge.edit_all_problem')
if not (user.has_perm('judge.see_private_problem') or edit_all_problem):
q = Q(is_public=True)
- if not (user.has_perm('judge.see_organization_problem') or edit_public_problem):
+ if not (
+ user.has_perm('judge.see_organization_problem') or edit_public_problem
+ ):
# Either not organization private or in the organization.
q &= Q(is_organization_private=False) | cls.organization_filter_q(
# Avoids needlessly joining Organization
- Profile.organizations.through.objects.filter(profile=user.profile).values('organization_id'),
+ Profile.organizations.through.objects.filter(
+ profile=user.profile
+ ).values('organization_id'),
)
if edit_own_problem:
q |= cls.organization_filter_q(
# Avoids needlessly joining Organization
- Organization.admins.through.objects.filter(profile=user.profile).values('organization_id'),
+ Organization.admins.through.objects.filter(
+ profile=user.profile
+ ).values('organization_id'),
)
# Authors, curators, and testers should always have access.
@@ -301,20 +475,38 @@ def get_visible_problems(cls, user):
def q_add_author_curator_tester(cls, q, profile):
# This is way faster than the obvious |= Q(authors=profile) et al. because we are not doing
# joins and forcing the user to clean it up with .distinct().
- q |= Exists(Problem.authors.through.objects.filter(problem=OuterRef('pk'), profile=profile))
- q |= Exists(Problem.curators.through.objects.filter(problem=OuterRef('pk'), profile=profile))
- q |= Exists(Problem.testers.through.objects.filter(problem=OuterRef('pk'), profile=profile))
+ q |= Exists(
+ Problem.authors.through.objects.filter(
+ problem=OuterRef('pk'), profile=profile
+ )
+ )
+ q |= Exists(
+ Problem.curators.through.objects.filter(
+ problem=OuterRef('pk'), profile=profile
+ )
+ )
+ q |= Exists(
+ Problem.testers.through.objects.filter(
+ problem=OuterRef('pk'), profile=profile
+ )
+ )
return q
@classmethod
def organization_filter_q(cls, queryset):
q = Q(is_organization_private=True)
- q &= Exists(Problem.organizations.through.objects.filter(problem=OuterRef('pk'), organization__in=queryset))
+ q &= Exists(
+ Problem.organizations.through.objects.filter(
+ problem=OuterRef('pk'), organization__in=queryset
+ )
+ )
return q
@classmethod
def get_public_problems(cls):
- return cls.objects.filter(is_public=True, is_organization_private=False).defer('description')
+ return cls.objects.filter(is_public=True, is_organization_private=False).defer(
+ 'description'
+ )
@classmethod
def get_editable_problems(cls, user):
@@ -324,7 +516,9 @@ def get_editable_problems(cls, user):
return cls.objects.all()
q = Q(authors=user.profile) | Q(curators=user.profile)
- q |= Q(is_organization_private=True, organizations__in=user.profile.admin_of.all())
+ q |= Q(
+ is_organization_private=True, organizations__in=user.profile.admin_of.all()
+ )
if user.has_perm('judge.edit_public_problem'):
q |= Q(is_public=True)
@@ -339,16 +533,23 @@ def get_absolute_url(self):
@cached_property
def author_ids(self):
- return Problem.authors.through.objects.filter(problem=self).values_list('profile_id', flat=True)
+ return Problem.authors.through.objects.filter(problem=self).values_list(
+ 'profile_id', flat=True
+ )
@cached_property
def editor_ids(self):
return self.author_ids.union(
- Problem.curators.through.objects.filter(problem=self).values_list('profile_id', flat=True))
+ Problem.curators.through.objects.filter(problem=self).values_list(
+ 'profile_id', flat=True
+ )
+ )
@cached_property
def tester_ids(self):
- return Problem.testers.through.objects.filter(problem=self).values_list('profile_id', flat=True)
+ return Problem.testers.through.objects.filter(problem=self).values_list(
+ 'profile_id', flat=True
+ )
@cached_property
def usable_common_names(self):
@@ -356,14 +557,18 @@ def usable_common_names(self):
@property
def usable_languages(self):
- return self.allowed_languages.filter(judges__in=self.judges.filter(online=True)).distinct()
+ return self.allowed_languages.filter(
+ judges__in=self.judges.filter(online=True)
+ ).distinct()
def translated_name(self, language):
if language in self._translated_name_cache:
return self._translated_name_cache[language]
# Hits database despite prefetch_related.
try:
- name = self.translations.filter(language=language).values_list('name', flat=True)[0]
+ name = self.translations.filter(language=language).values_list(
+ 'name', flat=True
+ )[0]
except IndexError:
name = self.name
self._translated_name_cache[language] = name
@@ -408,9 +613,13 @@ def update_stats(self):
def _get_limits(self, key):
global_limit = getattr(self, key)
- limits = {limit['language_id']: (limit['language__name'], limit[key])
- for limit in self.language_limits.values('language_id', 'language__name', key)
- if limit[key] != global_limit}
+ limits = {
+ limit['language_id']: (limit['language__name'], limit[key])
+ for limit in self.language_limits.values(
+ 'language_id', 'language__name', key
+ )
+ if limit[key] != global_limit
+ }
limit_ids = set(limits.keys())
common = []
@@ -466,7 +675,9 @@ def save(self, *args, **kwargs):
def is_solved_by(self, user):
# Return true if a full AC submission to the problem from the user exists.
- return self.submission_set.filter(user=user.profile, result='AC', points__gte=F('problem__points')).exists()
+ return self.submission_set.filter(
+ user=user.profile, result='AC', points__gte=F('problem__points')
+ ).exists()
def vote_permission_for_user(self, user):
if not user.is_authenticated:
@@ -507,11 +718,22 @@ class Meta:
class ProblemTranslation(models.Model):
- problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='translations', on_delete=CASCADE)
- language = models.CharField(verbose_name=_('language'), max_length=7, choices=settings.LANGUAGES)
- name = models.CharField(verbose_name=_('translated name'), max_length=100, db_index=True)
- description = models.TextField(verbose_name=_('translated description'),
- validators=[disallowed_characters_validator])
+ problem = models.ForeignKey(
+ Problem,
+ verbose_name=_('problem'),
+ related_name='translations',
+ on_delete=CASCADE,
+ )
+ language = models.CharField(
+ verbose_name=_('language'), max_length=7, choices=settings.LANGUAGES
+ )
+ name = models.CharField(
+ verbose_name=_('translated name'), max_length=100, db_index=True
+ )
+ description = models.TextField(
+ verbose_name=_('translated description'),
+ validators=[disallowed_characters_validator],
+ )
class Meta:
unique_together = ('problem', 'language')
@@ -520,9 +742,16 @@ class Meta:
class ProblemClarification(models.Model):
- problem = models.ForeignKey(Problem, verbose_name=_('clarified problem'), on_delete=CASCADE)
- description = models.TextField(verbose_name=_('clarification body'), validators=[disallowed_characters_validator])
- date = models.DateTimeField(verbose_name=_('clarification timestamp'), auto_now_add=True)
+ problem = models.ForeignKey(
+ Problem, verbose_name=_('clarified problem'), on_delete=CASCADE
+ )
+ description = models.TextField(
+ verbose_name=_('clarification body'),
+ validators=[disallowed_characters_validator],
+ )
+ date = models.DateTimeField(
+ verbose_name=_('clarification timestamp'), auto_now_add=True
+ )
class Meta:
verbose_name = _('problem clarification')
@@ -530,14 +759,29 @@ class Meta:
class LanguageLimit(models.Model):
- problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='language_limits', on_delete=CASCADE)
- language = models.ForeignKey(Language, verbose_name=_('language'), on_delete=CASCADE)
- time_limit = models.FloatField(verbose_name=_('time limit'),
- validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_TIME_LIMIT),
- MaxValueValidator(settings.DMOJ_PROBLEM_MAX_TIME_LIMIT)])
- memory_limit = models.IntegerField(verbose_name=_('memory limit'),
- validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_MEMORY_LIMIT),
- MaxValueValidator(settings.DMOJ_PROBLEM_MAX_MEMORY_LIMIT)])
+ problem = models.ForeignKey(
+ Problem,
+ verbose_name=_('problem'),
+ related_name='language_limits',
+ on_delete=CASCADE,
+ )
+ language = models.ForeignKey(
+ Language, verbose_name=_('language'), on_delete=CASCADE
+ )
+ time_limit = models.FloatField(
+ verbose_name=_('time limit'),
+ validators=[
+ MinValueValidator(settings.DMOJ_PROBLEM_MIN_TIME_LIMIT),
+ MaxValueValidator(settings.DMOJ_PROBLEM_MAX_TIME_LIMIT),
+ ],
+ )
+ memory_limit = models.IntegerField(
+ verbose_name=_('memory limit'),
+ validators=[
+ MinValueValidator(settings.DMOJ_PROBLEM_MIN_MEMORY_LIMIT),
+ MaxValueValidator(settings.DMOJ_PROBLEM_MAX_MEMORY_LIMIT),
+ ],
+ )
class Meta:
unique_together = ('problem', 'language')
@@ -546,12 +790,20 @@ class Meta:
class Solution(models.Model):
- problem = models.OneToOneField(Problem, on_delete=CASCADE, verbose_name=_('associated problem'),
- blank=True, related_name='solution')
+ problem = models.OneToOneField(
+ Problem,
+ on_delete=CASCADE,
+ verbose_name=_('associated problem'),
+ blank=True,
+ related_name='solution',
+ )
is_public = models.BooleanField(verbose_name=_('public visibility'), default=False)
publish_on = models.DateTimeField(verbose_name=_('publish date'))
authors = models.ManyToManyField(Profile, verbose_name=_('authors'), blank=True)
- content = models.TextField(verbose_name=_('editorial content'), validators=[disallowed_characters_validator])
+ content = models.TextField(
+ verbose_name=_('editorial content'),
+ validators=[disallowed_characters_validator],
+ )
def get_absolute_url(self):
problem = self.problem
@@ -573,9 +825,7 @@ def is_accessible_by(self, user):
return False
class Meta:
- permissions = (
- ('see_private_solution', _('See hidden solutions')),
- )
+ permissions = (('see_private_solution', _('See hidden solutions')),)
verbose_name = _('solution')
verbose_name_plural = _('solutions')
@@ -589,17 +839,37 @@ class ProblemPointsVote(models.Model):
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_USER_POINTS_VOTE),
],
)
- voter = models.ForeignKey(Profile, verbose_name=_('voter'), related_name='problem_points_votes', on_delete=CASCADE)
- problem = models.ForeignKey(Problem, verbose_name=_('problem'), related_name='problem_points_votes',
- on_delete=CASCADE)
- vote_time = models.DateTimeField(verbose_name=_('vote time'), help_text=_('The time this vote was cast.'),
- auto_now_add=True)
- note = models.TextField(verbose_name=_('note'), help_text=_('Justification for problem point value.'),
- max_length=8192, blank=True, default='')
+ voter = models.ForeignKey(
+ Profile,
+ verbose_name=_('voter'),
+ related_name='problem_points_votes',
+ on_delete=CASCADE,
+ )
+ problem = models.ForeignKey(
+ Problem,
+ verbose_name=_('problem'),
+ related_name='problem_points_votes',
+ on_delete=CASCADE,
+ )
+ vote_time = models.DateTimeField(
+ verbose_name=_('vote time'),
+ help_text=_('The time this vote was cast.'),
+ auto_now_add=True,
+ )
+ note = models.TextField(
+ verbose_name=_('note'),
+ help_text=_('Justification for problem point value.'),
+ max_length=8192,
+ blank=True,
+ default='',
+ )
class Meta:
verbose_name = _('problem vote')
verbose_name_plural = _('problem votes')
def __str__(self):
- return _('Points vote by %(voter)s for %(problem)s') % {'voter': self.voter, 'problem': self.problem}
+ return _('Points vote by %(voter)s for %(problem)s') % {
+ 'voter': self.voter,
+ 'problem': self.problem,
+ }
diff --git a/judge/models/problem_data.py b/judge/models/problem_data.py
index a00016a510..643488e8f4 100644
--- a/judge/models/problem_data.py
+++ b/judge/models/problem_data.py
@@ -6,7 +6,13 @@
from judge.utils.problem_data import ProblemDataStorage
-__all__ = ['problem_data_storage', 'problem_directory_file', 'ProblemData', 'ProblemTestCase', 'CHECKERS']
+__all__ = [
+ 'problem_data_storage',
+ 'problem_directory_file',
+ 'ProblemData',
+ 'ProblemTestCase',
+ 'CHECKERS',
+]
problem_data_storage = ProblemDataStorage()
@@ -32,20 +38,49 @@ def problem_directory_file(data, filename):
class ProblemData(models.Model):
- problem = models.OneToOneField('Problem', verbose_name=_('problem'), related_name='data_files',
- on_delete=models.CASCADE)
- zipfile = models.FileField(verbose_name=_('data zip file'), storage=problem_data_storage, null=True, blank=True,
- upload_to=problem_directory_file)
- generator = models.FileField(verbose_name=_('generator file'), storage=problem_data_storage, null=True, blank=True,
- upload_to=problem_directory_file)
- output_prefix = models.IntegerField(verbose_name=_('output prefix length'), blank=True, null=True)
- output_limit = models.IntegerField(verbose_name=_('output limit length'), blank=True, null=True)
- feedback = models.TextField(verbose_name=_('init.yml generation feedback'), blank=True)
- checker = models.CharField(max_length=10, verbose_name=_('checker'), choices=CHECKERS, blank=True)
- unicode = models.BooleanField(verbose_name=_('enable unicode'), null=True, blank=True)
- nobigmath = models.BooleanField(verbose_name=_('disable bigInteger / bigDecimal'), null=True, blank=True)
- checker_args = models.TextField(verbose_name=_('checker arguments'), blank=True,
- help_text=_('Checker arguments as a JSON object.'))
+ problem = models.OneToOneField(
+ 'Problem',
+ verbose_name=_('problem'),
+ related_name='data_files',
+ on_delete=models.CASCADE,
+ )
+ zipfile = models.FileField(
+ verbose_name=_('data zip file'),
+ storage=problem_data_storage,
+ null=True,
+ blank=True,
+ upload_to=problem_directory_file,
+ )
+ generator = models.FileField(
+ verbose_name=_('generator file'),
+ storage=problem_data_storage,
+ null=True,
+ blank=True,
+ upload_to=problem_directory_file,
+ )
+ output_prefix = models.IntegerField(
+ verbose_name=_('output prefix length'), blank=True, null=True
+ )
+ output_limit = models.IntegerField(
+ verbose_name=_('output limit length'), blank=True, null=True
+ )
+ feedback = models.TextField(
+ verbose_name=_('init.yml generation feedback'), blank=True
+ )
+ checker = models.CharField(
+ max_length=10, verbose_name=_('checker'), choices=CHECKERS, blank=True
+ )
+ unicode = models.BooleanField(
+ verbose_name=_('enable unicode'), null=True, blank=True
+ )
+ nobigmath = models.BooleanField(
+ verbose_name=_('disable bigInteger / bigDecimal'), null=True, blank=True
+ )
+ checker_args = models.TextField(
+ verbose_name=_('checker arguments'),
+ blank=True,
+ help_text=_('Checker arguments as a JSON object.'),
+ )
__original_zipfile = None
@@ -72,25 +107,48 @@ def _update_code(self, original, new):
if self.generator:
self.generator.name = _problem_directory_file(new, self.generator.name)
self.save()
+
_update_code.alters_data = True
class ProblemTestCase(models.Model):
- dataset = models.ForeignKey('Problem', verbose_name=_('problem data set'), related_name='cases',
- on_delete=models.CASCADE)
+ dataset = models.ForeignKey(
+ 'Problem',
+ verbose_name=_('problem data set'),
+ related_name='cases',
+ on_delete=models.CASCADE,
+ )
order = models.IntegerField(verbose_name=_('case position'))
- type = models.CharField(max_length=1, verbose_name=_('case type'),
- choices=(('C', _('Normal case')),
- ('S', _('Batch start')),
- ('E', _('Batch end'))),
- default='C')
- input_file = models.CharField(max_length=100, verbose_name=_('input file name'), blank=True)
- output_file = models.CharField(max_length=100, verbose_name=_('output file name'), blank=True)
+ type = models.CharField(
+ max_length=1,
+ verbose_name=_('case type'),
+ choices=(
+ ('C', _('Normal case')),
+ ('S', _('Batch start')),
+ ('E', _('Batch end')),
+ ),
+ default='C',
+ )
+ input_file = models.CharField(
+ max_length=100, verbose_name=_('input file name'), blank=True
+ )
+ output_file = models.CharField(
+ max_length=100, verbose_name=_('output file name'), blank=True
+ )
generator_args = models.TextField(verbose_name=_('generator arguments'), blank=True)
points = models.IntegerField(verbose_name=_('point value'), blank=True, null=True)
is_pretest = models.BooleanField(verbose_name=_('case is pretest?'))
- output_prefix = models.IntegerField(verbose_name=_('output prefix length'), blank=True, null=True)
- output_limit = models.IntegerField(verbose_name=_('output limit length'), blank=True, null=True)
- checker = models.CharField(max_length=10, verbose_name=_('checker'), choices=CHECKERS, blank=True)
- checker_args = models.TextField(verbose_name=_('checker arguments'), blank=True,
- help_text=_('Checker arguments as a JSON object.'))
+ output_prefix = models.IntegerField(
+ verbose_name=_('output prefix length'), blank=True, null=True
+ )
+ output_limit = models.IntegerField(
+ verbose_name=_('output limit length'), blank=True, null=True
+ )
+ checker = models.CharField(
+ max_length=10, verbose_name=_('checker'), choices=CHECKERS, blank=True
+ )
+ checker_args = models.TextField(
+ verbose_name=_('checker arguments'),
+ blank=True,
+ help_text=_('Checker arguments as a JSON object.'),
+ )
diff --git a/judge/models/profile.py b/judge/models/profile.py
index 859c30d47f..8c31664447 100644
--- a/judge/models/profile.py
+++ b/judge/models/profile.py
@@ -28,7 +28,13 @@
from judge.ratings import rating_class
from judge.utils.two_factor import webauthn_decode
-__all__ = ['Class', 'Organization', 'Profile', 'OrganizationRequest', 'WebAuthnCredential']
+__all__ = [
+ 'Class',
+ 'Organization',
+ 'Profile',
+ 'OrganizationRequest',
+ 'WebAuthnCredential',
+]
class EncryptedNullCharField(EncryptedCharField):
@@ -40,31 +46,70 @@ def get_prep_value(self, value):
class Organization(models.Model):
name = models.CharField(max_length=128, verbose_name=_('organization title'))
- slug = models.SlugField(max_length=128, verbose_name=_('organization slug'),
- help_text=_('Organization name shown in URLs.'))
- short_name = models.CharField(max_length=20, verbose_name=_('short name'),
- help_text=_('Displayed beside user name during contests.'))
+ slug = models.SlugField(
+ max_length=128,
+ verbose_name=_('organization slug'),
+ help_text=_('Organization name shown in URLs.'),
+ )
+ short_name = models.CharField(
+ max_length=20,
+ verbose_name=_('short name'),
+ help_text=_('Displayed beside user name during contests.'),
+ )
about = models.TextField(verbose_name=_('organization description'))
- admins = models.ManyToManyField('Profile', verbose_name=_('administrators'), related_name='admin_of',
- help_text=_('Those who can edit this organization.'))
- creation_date = models.DateTimeField(verbose_name=_('creation date'), auto_now_add=True)
- is_open = models.BooleanField(verbose_name=_('is open organization?'),
- help_text=_('Allow joining organization.'), default=True)
- slots = models.IntegerField(verbose_name=_('maximum size'), null=True, blank=True,
- help_text=_('Maximum amount of users in this organization, '
- 'only applicable to private organizations.'))
- access_code = models.CharField(max_length=7, help_text=_('Student access code.'),
- verbose_name=_('access code'), null=True, blank=True)
- logo_override_image = models.CharField(verbose_name=_('logo override image'), default='', max_length=150,
- blank=True,
- help_text=_('This image will replace the default site logo for users '
- 'viewing the organization.'))
- class_required = models.BooleanField(verbose_name=_('class membership required'), default=False,
- help_text=_('Whether members are compelled to select a class when joining.'))
+ admins = models.ManyToManyField(
+ 'Profile',
+ verbose_name=_('administrators'),
+ related_name='admin_of',
+ help_text=_('Those who can edit this organization.'),
+ )
+ creation_date = models.DateTimeField(
+ verbose_name=_('creation date'), auto_now_add=True
+ )
+ is_open = models.BooleanField(
+ verbose_name=_('is open organization?'),
+ help_text=_('Allow joining organization.'),
+ default=True,
+ )
+ slots = models.IntegerField(
+ verbose_name=_('maximum size'),
+ null=True,
+ blank=True,
+ help_text=_(
+ 'Maximum amount of users in this organization, '
+ 'only applicable to private organizations.'
+ ),
+ )
+ access_code = models.CharField(
+ max_length=7,
+ help_text=_('Student access code.'),
+ verbose_name=_('access code'),
+ null=True,
+ blank=True,
+ )
+ logo_override_image = models.CharField(
+ verbose_name=_('logo override image'),
+ default='',
+ max_length=150,
+ blank=True,
+ help_text=_(
+ 'This image will replace the default site logo for users '
+ 'viewing the organization.'
+ ),
+ )
+ class_required = models.BooleanField(
+ verbose_name=_('class membership required'),
+ default=False,
+ help_text=_('Whether members are compelled to select a class when joining.'),
+ )
def clean(self):
if self.class_required and self.is_open:
- raise ValidationError(_('Class membership cannot be enforced when organization has open enrollment.'))
+ raise ValidationError(
+ _(
+ 'Class membership cannot be enforced when organization has open enrollment.'
+ )
+ )
def __contains__(self, item):
if isinstance(item, int):
@@ -72,7 +117,9 @@ def __contains__(self, item):
elif isinstance(item, Profile):
return self.members.filter(id=item.id).exists()
else:
- raise TypeError('Organization membership test must be Profile or primary key.')
+ raise TypeError(
+ 'Organization membership test must be Profile or primary key.'
+ )
def __str__(self):
return self.name
@@ -100,19 +147,42 @@ class Meta:
class Class(models.Model):
- organization = models.ForeignKey(Organization, on_delete=models.CASCADE, verbose_name=_('organization'),
- help_text=_('The organization that this class belongs to.'),
- related_name='classes', related_query_name='class')
+ organization = models.ForeignKey(
+ Organization,
+ on_delete=models.CASCADE,
+ verbose_name=_('organization'),
+ help_text=_('The organization that this class belongs to.'),
+ related_name='classes',
+ related_query_name='class',
+ )
name = models.CharField(max_length=128, verbose_name=_('class name'), unique=True)
- slug = models.SlugField(max_length=128, verbose_name=_('class slug'), help_text=_('Class name shown in URLs.'))
+ slug = models.SlugField(
+ max_length=128,
+ verbose_name=_('class slug'),
+ help_text=_('Class name shown in URLs.'),
+ )
description = models.TextField(verbose_name=_('class description'), blank=True)
is_active = models.BooleanField(verbose_name=_('is class active'), default=True)
- access_code = models.CharField(max_length=7, verbose_name=_('access code'), null=True, blank=True,
- help_text=_('Student access code.'))
- admins = models.ManyToManyField('Profile', verbose_name=_('administrators'), related_name='class_admin_of',
- help_text=_('Those who can approve membership to this class.'))
- members = models.ManyToManyField('Profile', verbose_name=_('members'), blank=True,
- related_name='classes', related_query_name='class')
+ access_code = models.CharField(
+ max_length=7,
+ verbose_name=_('access code'),
+ null=True,
+ blank=True,
+ help_text=_('Student access code.'),
+ )
+ admins = models.ManyToManyField(
+ 'Profile',
+ verbose_name=_('administrators'),
+ related_name='class_admin_of',
+ help_text=_('Those who can approve membership to this class.'),
+ )
+ members = models.ManyToManyField(
+ 'Profile',
+ verbose_name=_('members'),
+ blank=True,
+ related_name='classes',
+ related_query_name='class',
+ )
@classmethod
def get_visible_classes(cls, user):
@@ -122,10 +192,15 @@ def get_visible_classes(cls, user):
if user.has_perm('judge.edit_all_organization'):
return cls.objects.all()
- return cls.objects.filter(contest__organizations__admins=user.profile) | cls.objects.filter(admins=user.profile)
+ return cls.objects.filter(
+ contest__organizations__admins=user.profile
+ ) | cls.objects.filter(admins=user.profile)
def __str__(self):
- return _('%(class)s in %(organization)s') % {'class': self.name, 'organization': self.organization.name}
+ return _('%(class)s in %(organization)s') % {
+ 'class': self.name,
+ 'organization': self.organization.name,
+ }
def get_absolute_url(self):
return reverse('class_home', args=self._url_args)
@@ -141,72 +216,161 @@ class Meta:
ordering = ['organization', 'name']
verbose_name = _('class')
verbose_name_plural = _('classes')
- constraints = [UniqueConstraint(fields=['name'], condition=Q(is_active=True), name='unique_active_name')]
+ constraints = [
+ UniqueConstraint(
+ fields=['name'], condition=Q(is_active=True), name='unique_active_name'
+ )
+ ]
class Profile(models.Model):
- user = models.OneToOneField(User, verbose_name=_('user associated'), on_delete=models.CASCADE)
+ user = models.OneToOneField(
+ User, verbose_name=_('user associated'), on_delete=models.CASCADE
+ )
about = models.TextField(verbose_name=_('self-description'), null=True, blank=True)
- timezone = models.CharField(max_length=50, verbose_name=_('time zone'), choices=TIMEZONE,
- default=settings.DEFAULT_USER_TIME_ZONE)
- language = models.ForeignKey('Language', verbose_name=_('preferred language'), on_delete=models.SET_DEFAULT,
- default=Language.get_default_language_pk)
+ timezone = models.CharField(
+ max_length=50,
+ verbose_name=_('time zone'),
+ choices=TIMEZONE,
+ default=settings.DEFAULT_USER_TIME_ZONE,
+ )
+ language = models.ForeignKey(
+ 'Language',
+ verbose_name=_('preferred language'),
+ on_delete=models.SET_DEFAULT,
+ default=Language.get_default_language_pk,
+ )
points = models.FloatField(default=0)
performance_points = models.FloatField(default=0)
problem_count = models.IntegerField(default=0)
- ace_theme = models.CharField(max_length=30, verbose_name=_('Ace theme'), choices=ACE_THEMES, default='auto')
- site_theme = models.CharField(max_length=10, verbose_name=_('site theme'), choices=SITE_THEMES, default='auto')
+ ace_theme = models.CharField(
+ max_length=30, verbose_name=_('Ace theme'), choices=ACE_THEMES, default='auto'
+ )
+ site_theme = models.CharField(
+ max_length=10, verbose_name=_('site theme'), choices=SITE_THEMES, default='auto'
+ )
last_access = models.DateTimeField(verbose_name=_('last access time'), default=now)
ip = models.GenericIPAddressField(verbose_name=_('last IP'), blank=True, null=True)
- organizations = SortedManyToManyField(Organization, verbose_name=_('organization'), blank=True,
- related_name='members', related_query_name='member')
- display_rank = models.CharField(max_length=10, default='user', verbose_name=_('display rank'),
- choices=(
- ('user', _('Normal User')),
- ('setter', _('Problem Setter')),
- ('admin', _('Admin'))))
- mute = models.BooleanField(verbose_name=_('comment mute'), help_text=_('Some users are at their best when silent.'),
- default=False)
- is_unlisted = models.BooleanField(verbose_name=_('unlisted user'), help_text=_('User will not be ranked.'),
- default=False)
+ organizations = SortedManyToManyField(
+ Organization,
+ verbose_name=_('organization'),
+ blank=True,
+ related_name='members',
+ related_query_name='member',
+ )
+ display_rank = models.CharField(
+ max_length=10,
+ default='user',
+ verbose_name=_('display rank'),
+ choices=(
+ ('user', _('Normal User')),
+ ('setter', _('Problem Setter')),
+ ('admin', _('Admin')),
+ ),
+ )
+ mute = models.BooleanField(
+ verbose_name=_('comment mute'),
+ help_text=_('Some users are at their best when silent.'),
+ default=False,
+ )
+ is_unlisted = models.BooleanField(
+ verbose_name=_('unlisted user'),
+ help_text=_('User will not be ranked.'),
+ default=False,
+ )
is_banned_from_problem_voting = models.BooleanField(
verbose_name=_('banned from voting on problem point values'),
help_text=_("User will not be able to vote on problems' point values."),
default=False,
)
rating = models.IntegerField(null=True, default=None)
- user_script = models.TextField(verbose_name=_('user script'), default='', blank=True, max_length=65536,
- help_text=_('User-defined JavaScript for site customization.'))
- current_contest = models.OneToOneField('ContestParticipation', verbose_name=_('current contest'),
- null=True, blank=True, related_name='+', on_delete=models.SET_NULL)
- math_engine = models.CharField(verbose_name=_('math engine'), choices=MATH_ENGINES_CHOICES, max_length=4,
- default=settings.MATHOID_DEFAULT_TYPE,
- help_text=_('The rendering engine used to render math.'))
- is_totp_enabled = models.BooleanField(verbose_name=_('TOTP 2FA enabled'), default=False,
- help_text=_('Check to enable TOTP-based two-factor authentication.'))
- is_webauthn_enabled = models.BooleanField(verbose_name=_('WebAuthn 2FA enabled'), default=False,
- help_text=_('Check to enable WebAuthn-based two-factor authentication.'))
- totp_key = EncryptedNullCharField(max_length=32, null=True, blank=True, verbose_name=_('TOTP key'),
- help_text=_('32-character Base32-encoded key for TOTP.'),
- validators=[RegexValidator('^$|^[A-Z2-7]{32}$',
- _('TOTP key must be empty or Base32.'))])
- scratch_codes = EncryptedNullCharField(max_length=255, null=True, blank=True, verbose_name=_('scratch codes'),
- help_text=_('JSON array of 16-character Base32-encoded codes '
- 'for scratch codes.'),
- validators=[
- RegexValidator(r'^(\[\])?$|^\[("[A-Z0-9]{16}", *)*"[A-Z0-9]{16}"\]$',
- _('Scratch codes must be empty or a JSON array of '
- '16-character Base32 codes.'))])
- last_totp_timecode = models.IntegerField(verbose_name=_('last TOTP timecode'), default=0)
- api_token = models.CharField(max_length=64, null=True, verbose_name=_('API token'),
- help_text=_('64-character hex-encoded API access token.'),
- validators=[RegexValidator('^[a-f0-9]{64}$',
- _('API token must be None or hexadecimal'))])
- notes = models.TextField(verbose_name=_('internal notes'), null=True, blank=True,
- help_text=_('Notes for administrators regarding this user.'))
- data_last_downloaded = models.DateTimeField(verbose_name=_('last data download time'), null=True, blank=True)
- username_display_override = models.CharField(max_length=100, blank=True, verbose_name=_('display name override'),
- help_text=_('Name displayed in place of username.'))
+ user_script = models.TextField(
+ verbose_name=_('user script'),
+ default='',
+ blank=True,
+ max_length=65536,
+ help_text=_('User-defined JavaScript for site customization.'),
+ )
+ current_contest = models.OneToOneField(
+ 'ContestParticipation',
+ verbose_name=_('current contest'),
+ null=True,
+ blank=True,
+ related_name='+',
+ on_delete=models.SET_NULL,
+ )
+ math_engine = models.CharField(
+ verbose_name=_('math engine'),
+ choices=MATH_ENGINES_CHOICES,
+ max_length=4,
+ default=settings.MATHOID_DEFAULT_TYPE,
+ help_text=_('The rendering engine used to render math.'),
+ )
+ is_totp_enabled = models.BooleanField(
+ verbose_name=_('TOTP 2FA enabled'),
+ default=False,
+ help_text=_('Check to enable TOTP-based two-factor authentication.'),
+ )
+ is_webauthn_enabled = models.BooleanField(
+ verbose_name=_('WebAuthn 2FA enabled'),
+ default=False,
+ help_text=_('Check to enable WebAuthn-based two-factor authentication.'),
+ )
+ totp_key = EncryptedNullCharField(
+ max_length=32,
+ null=True,
+ blank=True,
+ verbose_name=_('TOTP key'),
+ help_text=_('32-character Base32-encoded key for TOTP.'),
+ validators=[
+ RegexValidator('^$|^[A-Z2-7]{32}$', _('TOTP key must be empty or Base32.'))
+ ],
+ )
+ scratch_codes = EncryptedNullCharField(
+ max_length=255,
+ null=True,
+ blank=True,
+ verbose_name=_('scratch codes'),
+ help_text=_(
+ 'JSON array of 16-character Base32-encoded codes ' 'for scratch codes.'
+ ),
+ validators=[
+ RegexValidator(
+ r'^(\[\])?$|^\[("[A-Z0-9]{16}", *)*"[A-Z0-9]{16}"\]$',
+ _(
+ 'Scratch codes must be empty or a JSON array of '
+ '16-character Base32 codes.'
+ ),
+ )
+ ],
+ )
+ last_totp_timecode = models.IntegerField(
+ verbose_name=_('last TOTP timecode'), default=0
+ )
+ api_token = models.CharField(
+ max_length=64,
+ null=True,
+ verbose_name=_('API token'),
+ help_text=_('64-character hex-encoded API access token.'),
+ validators=[
+ RegexValidator('^[a-f0-9]{64}$', _('API token must be None or hexadecimal'))
+ ],
+ )
+ notes = models.TextField(
+ verbose_name=_('internal notes'),
+ null=True,
+ blank=True,
+ help_text=_('Notes for administrators regarding this user.'),
+ )
+ data_last_downloaded = models.DateTimeField(
+ verbose_name=_('last data download time'), null=True, blank=True
+ )
+ username_display_override = models.CharField(
+ max_length=100,
+ blank=True,
+ verbose_name=_('display name override'),
+ help_text=_('Name displayed in place of username.'),
+ )
@cached_property
def organization(self):
@@ -224,7 +388,9 @@ def display_name(self):
@cached_property
def has_any_solves(self):
- return self.submission_set.filter(result='AC', case_points__gte=F('case_total')).exists()
+ return self.submission_set.filter(
+ result='AC', case_points__gte=F('case_total')
+ ).exists()
@cached_property
def resolved_ace_theme(self):
@@ -241,22 +407,36 @@ def resolved_ace_theme(self):
def calculate_points(self, table=_pp_table):
from judge.models import Problem
+
public_problems = Problem.get_public_problems()
data = (
- public_problems.filter(submission__user=self, submission__points__isnull=False)
- .annotate(max_points=Max('submission__points')).order_by('-max_points')
- .values_list('max_points', flat=True).filter(max_points__gt=0)
+ public_problems.filter(
+ submission__user=self, submission__points__isnull=False
+ )
+ .annotate(max_points=Max('submission__points'))
+ .order_by('-max_points')
+ .values_list('max_points', flat=True)
+ .filter(max_points__gt=0)
)
bonus_function = settings.DMOJ_PP_BONUS_FUNCTION
points = sum(data)
entries = min(len(data), len(table))
problems = (
- public_problems.filter(submission__user=self, submission__result='AC',
- submission__case_points__gte=F('submission__case_total'))
- .values('id').distinct().count()
+ public_problems.filter(
+ submission__user=self,
+ submission__result='AC',
+ submission__case_points__gte=F('submission__case_total'),
+ )
+ .values('id')
+ .distinct()
+ .count()
)
pp = sum(map(mul, table[:entries], data[:entries])) + bonus_function(problems)
- if self.points != points or problems != self.problem_count or self.performance_points != pp:
+ if (
+ self.points != points
+ or problems != self.problem_count
+ or self.performance_points != pp
+ ):
self.points = points
self.problem_count = problems
self.performance_points = pp
@@ -267,7 +447,9 @@ def calculate_points(self, table=_pp_table):
def generate_api_token(self):
secret = secrets.token_bytes(32)
- self.api_token = hmac.new(force_bytes(settings.SECRET_KEY), msg=secret, digestmod='sha256').hexdigest()
+ self.api_token = hmac.new(
+ force_bytes(settings.SECRET_KEY), msg=secret, digestmod='sha256'
+ ).hexdigest()
self.save(update_fields=['api_token'])
token = base64.urlsafe_b64encode(struct.pack('>I32s', self.user.id, secret))
return token.decode('utf-8')
@@ -276,8 +458,13 @@ def generate_api_token(self):
def generate_scratch_codes(self):
def generate_scratch_code():
- return ''.join(secrets.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') for _ in range(16))
- codes = [generate_scratch_code() for _ in range(settings.DMOJ_SCRATCH_CODES_COUNT)]
+ return ''.join(
+ secrets.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567') for _ in range(16)
+ )
+
+ codes = [
+ generate_scratch_code() for _ in range(settings.DMOJ_SCRATCH_CODES_COUNT)
+ ]
self.scratch_codes = json.dumps(codes)
self.save(update_fields=['scratch_codes'])
return codes
@@ -292,7 +479,9 @@ def remove_contest(self):
def update_contest(self):
contest = self.current_contest
- if contest is not None and (contest.ended or not contest.contest.is_accessible_by(self.user)):
+ if contest is not None and (
+ contest.ended or not contest.contest.is_accessible_by(self.user)
+ ):
self.remove_contest()
update_contest.alters_data = True
@@ -300,8 +489,13 @@ def update_contest(self):
def check_totp_code(self, code):
totp = pyotp.TOTP(self.totp_key)
now_timecode = totp.timecode(timezone.now())
- min_timecode = max(self.last_totp_timecode + 1, now_timecode - settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES)
- for timecode in range(min_timecode, now_timecode + settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES + 1):
+ min_timecode = max(
+ self.last_totp_timecode + 1,
+ now_timecode - settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES,
+ )
+ for timecode in range(
+ min_timecode, now_timecode + settings.DMOJ_TOTP_TOLERANCE_HALF_MINUTES + 1
+ ):
if strings_equal(code, totp.generate_otp(timecode)):
self.last_totp_timecode = timecode
self.save(update_fields=['last_totp_timecode'])
@@ -317,9 +511,14 @@ def __str__(self):
return self.user.username
@classmethod
- def get_user_css_class(cls, display_rank, rating, rating_colors=settings.DMOJ_RATING_COLORS):
+ def get_user_css_class(
+ cls, display_rank, rating, rating_colors=settings.DMOJ_RATING_COLORS
+ ):
if rating_colors:
- return 'rating %s %s' % (rating_class(rating) if rating is not None else 'rate-none', display_rank)
+ return 'rating %s %s' % (
+ rating_class(rating) if rating is not None else 'rate-none',
+ display_rank,
+ )
return display_rank
@cached_property
@@ -328,7 +527,11 @@ def css_class(self):
@cached_property
def webauthn_id(self):
- return hmac.new(force_bytes(settings.SECRET_KEY), msg=b'webauthn:%d' % (self.id,), digestmod='sha256').digest()
+ return hmac.new(
+ force_bytes(settings.SECRET_KEY),
+ msg=b'webauthn:%d' % (self.id,),
+ digestmod='sha256',
+ ).digest()
class Meta:
permissions = (
@@ -346,10 +549,16 @@ class Meta:
class WebAuthnCredential(models.Model):
- user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='webauthn_credentials',
- on_delete=models.CASCADE)
+ user = models.ForeignKey(
+ Profile,
+ verbose_name=_('user'),
+ related_name='webauthn_credentials',
+ on_delete=models.CASCADE,
+ )
name = models.CharField(verbose_name=_('device name'), max_length=100)
- cred_id = models.CharField(verbose_name=_('credential ID'), max_length=255, unique=True)
+ cred_id = models.CharField(
+ verbose_name=_('credential ID'), max_length=255, unique=True
+ )
public_key = models.TextField(verbose_name=_('public key'))
counter = models.BigIntegerField(verbose_name=_('sign counter'))
@@ -377,22 +586,40 @@ class Meta:
class OrganizationRequest(models.Model):
- user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='requests', on_delete=models.CASCADE)
- organization = models.ForeignKey(Organization, verbose_name=_('organization'), related_name='requests',
- on_delete=models.CASCADE)
+ user = models.ForeignKey(
+ Profile,
+ verbose_name=_('user'),
+ related_name='requests',
+ on_delete=models.CASCADE,
+ )
+ organization = models.ForeignKey(
+ Organization,
+ verbose_name=_('organization'),
+ related_name='requests',
+ on_delete=models.CASCADE,
+ )
time = models.DateTimeField(verbose_name=_('request time'), auto_now_add=True)
- state = models.CharField(max_length=1, verbose_name=_('state'), choices=(
- ('P', _('Pending')),
- ('A', _('Approved')),
- ('R', _('Rejected')),
- ))
- request_class = models.ForeignKey(Class, verbose_name=_('class'), on_delete=models.CASCADE, null=True, blank=True)
+ state = models.CharField(
+ max_length=1,
+ verbose_name=_('state'),
+ choices=(
+ ('P', _('Pending')),
+ ('A', _('Approved')),
+ ('R', _('Rejected')),
+ ),
+ )
+ request_class = models.ForeignKey(
+ Class, verbose_name=_('class'), on_delete=models.CASCADE, null=True, blank=True
+ )
reason = models.TextField(verbose_name=_('reason'))
def clean(self):
if self.organization.class_required and self.request_class is None:
raise ValidationError('Organization requires a class to be specified')
- if self.request_class and self.organization_id != self.request_class.organization_id:
+ if (
+ self.request_class
+ and self.organization_id != self.request_class.organization_id
+ ):
raise ValidationError('Class must be part of the organization')
class Meta:
diff --git a/judge/models/runtime.py b/judge/models/runtime.py
index 313cbe79ea..6101346767 100644
--- a/judge/models/runtime.py
+++ b/judge/models/runtime.py
@@ -16,34 +16,78 @@
class Language(models.Model):
- key = models.CharField(max_length=6, verbose_name=_('short identifier'),
- help_text=_('The identifier for this language; the same as its executor id for judges.'),
- unique=True)
- name = models.CharField(max_length=20, verbose_name=_('long name'),
- help_text=_('Longer name for the language, e.g. "Python 2" or "C++11".'))
- short_name = models.CharField(max_length=10, verbose_name=_('short name'),
- help_text=_('More readable, but short, name to display publicly; e.g. "PY2" or '
- '"C++11". If left blank, it will default to the '
- 'short identifier.'),
- null=True, blank=True)
- common_name = models.CharField(max_length=10, verbose_name=_('common name'),
- help_text=_('Common name for the language. For example, the common name for C++03, '
- 'C++11, and C++14 would be "C++".'))
- ace = models.CharField(max_length=20, verbose_name=_('ace mode name'),
- help_text=_('Language ID for Ace.js editor highlighting, appended to "mode-" to determine '
- 'the Ace JavaScript file to use, e.g., "python".'))
- pygments = models.CharField(max_length=20, verbose_name=_('pygments name'),
- help_text=_('Language ID for Pygments highlighting in source windows.'))
- template = models.TextField(verbose_name=_('code template'),
- help_text=_('Code template to display in submission editor.'), blank=True)
- info = models.CharField(max_length=50, verbose_name=_('runtime info override'), blank=True,
- help_text=_("Do not set this unless you know what you're doing! It will override the "
- 'usually more specific, judge-provided runtime info!'))
- description = models.TextField(verbose_name=_('language description'),
- help_text=_('Use this field to inform users of quirks with your environment, '
- 'additional restrictions, etc.'), blank=True)
- extension = models.CharField(max_length=10, verbose_name=_('extension'),
- help_text=_('The extension of source files, e.g., "py" or "cpp".'))
+ key = models.CharField(
+ max_length=6,
+ verbose_name=_('short identifier'),
+ help_text=_(
+ 'The identifier for this language; the same as its executor id for judges.'
+ ),
+ unique=True,
+ )
+ name = models.CharField(
+ max_length=20,
+ verbose_name=_('long name'),
+ help_text=_('Longer name for the language, e.g. "Python 2" or "C++11".'),
+ )
+ short_name = models.CharField(
+ max_length=10,
+ verbose_name=_('short name'),
+ help_text=_(
+ 'More readable, but short, name to display publicly; e.g. "PY2" or '
+ '"C++11". If left blank, it will default to the '
+ 'short identifier.'
+ ),
+ null=True,
+ blank=True,
+ )
+ common_name = models.CharField(
+ max_length=10,
+ verbose_name=_('common name'),
+ help_text=_(
+ 'Common name for the language. For example, the common name for C++03, '
+ 'C++11, and C++14 would be "C++".'
+ ),
+ )
+ ace = models.CharField(
+ max_length=20,
+ verbose_name=_('ace mode name'),
+ help_text=_(
+ 'Language ID for Ace.js editor highlighting, appended to "mode-" to determine '
+ 'the Ace JavaScript file to use, e.g., "python".'
+ ),
+ )
+ pygments = models.CharField(
+ max_length=20,
+ verbose_name=_('pygments name'),
+ help_text=_('Language ID for Pygments highlighting in source windows.'),
+ )
+ template = models.TextField(
+ verbose_name=_('code template'),
+ help_text=_('Code template to display in submission editor.'),
+ blank=True,
+ )
+ info = models.CharField(
+ max_length=50,
+ verbose_name=_('runtime info override'),
+ blank=True,
+ help_text=_(
+ "Do not set this unless you know what you're doing! It will override the "
+ 'usually more specific, judge-provided runtime info!'
+ ),
+ )
+ description = models.TextField(
+ verbose_name=_('language description'),
+ help_text=_(
+ 'Use this field to inform users of quirks with your environment, '
+ 'additional restrictions, etc.'
+ ),
+ blank=True,
+ )
+ extension = models.CharField(
+ max_length=10,
+ verbose_name=_('extension'),
+ help_text=_('The extension of source files, e.g., "py" or "cpp".'),
+ )
def runtime_versions(self):
runtimes = OrderedDict()
@@ -52,13 +96,17 @@ def runtime_versions(self):
id = runtime.name
if id not in runtimes:
runtimes[id] = set()
- if not runtime.version: # empty str == error determining version on judge side
+ if (
+ not runtime.version
+ ): # empty str == error determining version on judge side
continue
runtimes[id].add(runtime.version)
lang_versions = []
for id, version_list in runtimes.items():
- lang_versions.append((id, sorted(version_list, key=lambda a: tuple(map(int, a.split('.'))))))
+ lang_versions.append(
+ (id, sorted(version_list, key=lambda a: tuple(map(int, a.split('.')))))
+ )
return lang_versions
@classmethod
@@ -90,7 +138,9 @@ def display_name(self):
@classmethod
def get_python3(cls):
# We really need a default language, and this app is in Python 3
- return Language.objects.get_or_create(key='PY3', defaults={'name': 'Python 3'})[0]
+ return Language.objects.get_or_create(key='PY3', defaults={'name': 'Python 3'})[
+ 0
+ ]
def get_absolute_url(self):
return reverse('runtime_list') + '#' + self.key
@@ -113,33 +163,69 @@ class Meta:
class RuntimeVersion(models.Model):
- language = models.ForeignKey(Language, verbose_name=_('language to which this runtime belongs'), on_delete=CASCADE)
- judge = models.ForeignKey('Judge', verbose_name=_('judge on which this runtime exists'), on_delete=CASCADE)
+ language = models.ForeignKey(
+ Language,
+ verbose_name=_('language to which this runtime belongs'),
+ on_delete=CASCADE,
+ )
+ judge = models.ForeignKey(
+ 'Judge', verbose_name=_('judge on which this runtime exists'), on_delete=CASCADE
+ )
name = models.CharField(max_length=64, verbose_name=_('runtime name'))
- version = models.CharField(max_length=64, verbose_name=_('runtime version'), blank=True)
- priority = models.IntegerField(verbose_name=_('order in which to display this runtime'), default=0)
+ version = models.CharField(
+ max_length=64, verbose_name=_('runtime version'), blank=True
+ )
+ priority = models.IntegerField(
+ verbose_name=_('order in which to display this runtime'), default=0
+ )
class Judge(models.Model):
- name = models.CharField(max_length=50, verbose_name=_('judge name'), help_text=_('Server name, hostname-style.'),
- unique=True)
- created = models.DateTimeField(auto_now_add=True, verbose_name=_('time of creation'))
- auth_key = models.CharField(max_length=100, help_text=_('A key to authenticate this judge.'),
- verbose_name=_('authentication key'))
- is_blocked = models.BooleanField(verbose_name=_('block judge'), default=False,
- help_text=_('Whether this judge should be blocked from connecting, '
- 'even if its key is correct.'))
- is_disabled = models.BooleanField(verbose_name=_('disable judge'), default=False,
- help_text=_('Whether this judge should be removed from judging queue.'))
+ name = models.CharField(
+ max_length=50,
+ verbose_name=_('judge name'),
+ help_text=_('Server name, hostname-style.'),
+ unique=True,
+ )
+ created = models.DateTimeField(
+ auto_now_add=True, verbose_name=_('time of creation')
+ )
+ auth_key = models.CharField(
+ max_length=100,
+ help_text=_('A key to authenticate this judge.'),
+ verbose_name=_('authentication key'),
+ )
+ is_blocked = models.BooleanField(
+ verbose_name=_('block judge'),
+ default=False,
+ help_text=_(
+ 'Whether this judge should be blocked from connecting, '
+ 'even if its key is correct.'
+ ),
+ )
+ is_disabled = models.BooleanField(
+ verbose_name=_('disable judge'),
+ default=False,
+ help_text=_('Whether this judge should be removed from judging queue.'),
+ )
online = models.BooleanField(verbose_name=_('judge online status'), default=False)
start_time = models.DateTimeField(verbose_name=_('judge start time'), null=True)
ping = models.FloatField(verbose_name=_('response time'), null=True)
- load = models.FloatField(verbose_name=_('system load'), null=True,
- help_text=_('Load for the last minute, divided by processors to be fair.'))
+ load = models.FloatField(
+ verbose_name=_('system load'),
+ null=True,
+ help_text=_('Load for the last minute, divided by processors to be fair.'),
+ )
description = models.TextField(blank=True, verbose_name=_('description'))
- last_ip = models.GenericIPAddressField(verbose_name=_('last connected IP'), blank=True, null=True)
- problems = models.ManyToManyField('Problem', verbose_name=_('problems'), related_name='judges')
- runtimes = models.ManyToManyField(Language, verbose_name=_('judges'), related_name='judges')
+ last_ip = models.GenericIPAddressField(
+ verbose_name=_('last connected IP'), blank=True, null=True
+ )
+ problems = models.ManyToManyField(
+ 'Problem', verbose_name=_('problems'), related_name='judges'
+ )
+ runtimes = models.ManyToManyField(
+ Language, verbose_name=_('judges'), related_name='judges'
+ )
def __str__(self):
return self.name
@@ -158,9 +244,11 @@ def toggle_disabled(self):
@classmethod
def runtime_versions(cls):
- qs = (RuntimeVersion.objects.filter(judge__online=True)
- .values('judge__name', 'language__key', 'language__name', 'version', 'name')
- .order_by('language__key', 'priority'))
+ qs = (
+ RuntimeVersion.objects.filter(judge__online=True)
+ .values('judge__name', 'language__key', 'language__name', 'version', 'name')
+ .order_by('language__key', 'priority')
+ )
ret = defaultdict(OrderedDict)
diff --git a/judge/models/submission.py b/judge/models/submission.py
index 5aa6c67659..913b882cd5 100644
--- a/judge/models/submission.py
+++ b/judge/models/submission.py
@@ -64,30 +64,72 @@ class Submission(models.Model):
'AB': _('Aborted'),
}
- user = models.ForeignKey(Profile, verbose_name=_('user'), on_delete=models.CASCADE, db_index=False)
- problem = models.ForeignKey(Problem, verbose_name=_('problem'), on_delete=models.CASCADE, db_index=False)
- date = models.DateTimeField(verbose_name=_('submission time'), auto_now_add=True, db_index=True)
+ user = models.ForeignKey(
+ Profile, verbose_name=_('user'), on_delete=models.CASCADE, db_index=False
+ )
+ problem = models.ForeignKey(
+ Problem, verbose_name=_('problem'), on_delete=models.CASCADE, db_index=False
+ )
+ date = models.DateTimeField(
+ verbose_name=_('submission time'), auto_now_add=True, db_index=True
+ )
time = models.FloatField(verbose_name=_('execution time'), null=True)
memory = models.FloatField(verbose_name=_('memory usage'), null=True)
points = models.FloatField(verbose_name=_('points granted'), null=True)
- language = models.ForeignKey(Language, verbose_name=_('submission language'),
- on_delete=models.CASCADE, db_index=False)
- status = models.CharField(verbose_name=_('status'), max_length=2, choices=STATUS, default='QU', db_index=True)
- result = models.CharField(verbose_name=_('result'), max_length=3, choices=SUBMISSION_RESULT,
- default=None, null=True, blank=True)
+ language = models.ForeignKey(
+ Language,
+ verbose_name=_('submission language'),
+ on_delete=models.CASCADE,
+ db_index=False,
+ )
+ status = models.CharField(
+ verbose_name=_('status'),
+ max_length=2,
+ choices=STATUS,
+ default='QU',
+ db_index=True,
+ )
+ result = models.CharField(
+ verbose_name=_('result'),
+ max_length=3,
+ choices=SUBMISSION_RESULT,
+ default=None,
+ null=True,
+ blank=True,
+ )
error = models.TextField(verbose_name=_('compile errors'), null=True, blank=True)
current_testcase = models.IntegerField(default=0)
batch = models.BooleanField(verbose_name=_('batched cases'), default=False)
case_points = models.FloatField(verbose_name=_('test case points'), default=0)
case_total = models.FloatField(verbose_name=_('test case total points'), default=0)
- judged_on = models.ForeignKey('Judge', verbose_name=_('judged on'), null=True, blank=True,
- on_delete=models.SET_NULL)
- judged_date = models.DateTimeField(verbose_name=_('submission judge time'), default=None, null=True)
- rejudged_date = models.DateTimeField(verbose_name=_('last rejudge date by admin'), null=True, blank=True)
- is_pretested = models.BooleanField(verbose_name=_('was ran on pretests only'), default=False)
- contest_object = models.ForeignKey('Contest', verbose_name=_('contest'), null=True, blank=True,
- on_delete=models.SET_NULL, related_name='+', db_index=False)
- locked_after = models.DateTimeField(verbose_name=_('submission lock'), null=True, blank=True)
+ judged_on = models.ForeignKey(
+ 'Judge',
+ verbose_name=_('judged on'),
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ )
+ judged_date = models.DateTimeField(
+ verbose_name=_('submission judge time'), default=None, null=True
+ )
+ rejudged_date = models.DateTimeField(
+ verbose_name=_('last rejudge date by admin'), null=True, blank=True
+ )
+ is_pretested = models.BooleanField(
+ verbose_name=_('was ran on pretests only'), default=False
+ )
+ contest_object = models.ForeignKey(
+ 'Contest',
+ verbose_name=_('contest'),
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name='+',
+ db_index=False,
+ )
+ locked_after = models.DateTimeField(
+ verbose_name=_('submission lock'), null=True, blank=True
+ )
@classmethod
def result_class_from_code(cls, result, case_points, case_total):
@@ -102,7 +144,9 @@ def result_class(self):
# This exists to save all these conditionals from being executed (slowly) in each row.html template
if self.status in ('IE', 'CE'):
return self.status
- return Submission.result_class_from_code(self.result, self.case_points, self.case_total)
+ return Submission.result_class_from_code(
+ self.result, self.case_points, self.case_total
+ )
@property
def memory_bytes(self):
@@ -120,7 +164,9 @@ def long_status(self):
def is_locked(self):
return self.locked_after is not None and self.locked_after < timezone.now()
- def judge(self, *args, rejudge=False, force_judge=False, rejudge_user=None, **kwargs):
+ def judge(
+ self, *args, rejudge=False, force_judge=False, rejudge_user=None, **kwargs
+ ):
if force_judge or not self.is_locked:
if rejudge:
with revisions.create_revision(manage_manually=True):
@@ -150,20 +196,29 @@ def can_see_detail(self, user):
return True
elif source_visibility == SubmissionSourceAccess.ALWAYS:
return True
- elif source_visibility == SubmissionSourceAccess.SOLVED and \
- (self.problem.is_public or self.problem.testers.filter(id=profile.id).exists()) and \
- self.problem.is_solved_by(user):
+ elif (
+ source_visibility == SubmissionSourceAccess.SOLVED
+ and (
+ self.problem.is_public
+ or self.problem.testers.filter(id=profile.id).exists()
+ )
+ and self.problem.is_solved_by(user)
+ ):
return True
- elif source_visibility == SubmissionSourceAccess.ONLY_OWN and \
- self.problem.testers.filter(id=profile.id).exists():
+ elif (
+ source_visibility == SubmissionSourceAccess.ONLY_OWN
+ and self.problem.testers.filter(id=profile.id).exists()
+ ):
return True
contest = self.contest_object
# If user is an author or curator of the contest the submission was made in, or they can see in-contest subs
if contest is not None and (
- user.profile.id in contest.editor_ids or
- contest.view_contest_submissions.filter(id=user.profile.id).exists() or
- (contest.tester_see_submissions and user.profile.id in contest.tester_ids)
+ user.profile.id in contest.editor_ids
+ or contest.view_contest_submissions.filter(id=user.profile.id).exists()
+ or (
+ contest.tester_see_submissions and user.profile.id in contest.tester_ids
+ )
):
return True
@@ -176,8 +231,12 @@ def update_contest(self):
return
contest_problem = contest.problem
- contest.points = round(self.case_points / self.case_total * contest_problem.points
- if self.case_total > 0 else 0, 3)
+ contest.points = round(
+ self.case_points / self.case_total * contest_problem.points
+ if self.case_total > 0
+ else 0,
+ 3,
+ )
if not contest_problem.partial and contest.points != contest_problem.points:
contest.points = 0
contest.save()
@@ -196,7 +255,9 @@ def contest_key(self):
def __str__(self):
return _('Submission %(id)d of %(problem)s by %(user)s') % {
- 'id': self.id, 'problem': self.problem, 'user': self.user.user.username,
+ 'id': self.id,
+ 'problem': self.problem,
+ 'user': self.user.user.username,
}
def get_absolute_url(self):
@@ -211,8 +272,14 @@ def contest_or_none(self):
@classmethod
def get_id_secret(cls, sub_id):
- return (hmac.new(utf8bytes(settings.EVENT_DAEMON_SUBMISSION_KEY), b'%d' % sub_id, hashlib.sha512)
- .hexdigest()[:16] + '%08x' % sub_id)
+ return (
+ hmac.new(
+ utf8bytes(settings.EVENT_DAEMON_SUBMISSION_KEY),
+ b'%d' % sub_id,
+ hashlib.sha512,
+ ).hexdigest()[:16]
+ + '%08x' % sub_id
+ )
@cached_property
def id_secret(self):
@@ -234,33 +301,33 @@ class Meta:
indexes = [
# For problem submission rankings
models.Index(fields=['problem', 'user', '-points', '-time']),
-
# For contest problem submission rankings
- models.Index(fields=['contest_object', 'problem', 'user', '-points', '-time']),
-
+ models.Index(
+ fields=['contest_object', 'problem', 'user', '-points', '-time']
+ ),
# For main submission list filtering by some combination of result and language
models.Index(fields=['result', '-id']),
models.Index(fields=['result', 'language', '-id']),
models.Index(fields=['language', '-id']),
-
# For filtered main submission list result charts
models.Index(fields=['result', 'problem']),
models.Index(fields=['language', 'problem', 'result']),
-
# For problem submissions result chart
models.Index(fields=['problem', 'result']),
-
# For user_attempted_ids and own problem submissions result chart
models.Index(fields=['user', 'problem', 'result']),
-
# For user_completed_ids
models.Index(fields=['user', 'result']),
]
class SubmissionSource(models.Model):
- submission = models.OneToOneField(Submission, on_delete=models.CASCADE, verbose_name=_('associated submission'),
- related_name='source')
+ submission = models.OneToOneField(
+ Submission,
+ on_delete=models.CASCADE,
+ verbose_name=_('associated submission'),
+ related_name='source',
+ )
source = models.TextField(verbose_name=_('source code'), max_length=65536)
def __str__(self):
@@ -275,17 +342,28 @@ class Meta:
class SubmissionTestCase(models.Model):
RESULT = SUBMISSION_RESULT
- submission = models.ForeignKey(Submission, verbose_name=_('associated submission'), db_index=False,
- related_name='test_cases', on_delete=models.CASCADE)
+ submission = models.ForeignKey(
+ Submission,
+ verbose_name=_('associated submission'),
+ db_index=False,
+ related_name='test_cases',
+ on_delete=models.CASCADE,
+ )
case = models.IntegerField(verbose_name=_('test case ID'))
- status = models.CharField(max_length=3, verbose_name=_('status flag'), choices=SUBMISSION_RESULT)
+ status = models.CharField(
+ max_length=3, verbose_name=_('status flag'), choices=SUBMISSION_RESULT
+ )
time = models.FloatField(verbose_name=_('execution time'), null=True)
memory = models.FloatField(verbose_name=_('memory usage'), null=True)
points = models.FloatField(verbose_name=_('points granted'), null=True)
total = models.FloatField(verbose_name=_('points possible'), null=True)
batch = models.IntegerField(verbose_name=_('batch number'), null=True)
- feedback = models.CharField(max_length=50, verbose_name=_('judging feedback'), blank=True)
- extended_feedback = models.TextField(verbose_name=_('extended judging feedback'), blank=True)
+ feedback = models.CharField(
+ max_length=50, verbose_name=_('judging feedback'), blank=True
+ )
+ extended_feedback = models.TextField(
+ verbose_name=_('extended judging feedback'), blank=True
+ )
output = models.TextField(verbose_name=_('program output'), blank=True)
@property
diff --git a/judge/models/tests/test_blogpost.py b/judge/models/tests/test_blogpost.py
index 1beadde289..c051f3b16a 100644
--- a/judge/models/tests/test_blogpost.py
+++ b/judge/models/tests/test_blogpost.py
@@ -7,18 +7,20 @@ class BlogPostTestCase(CommonDataMixin, TestCase):
@classmethod
def setUpTestData(self):
super().setUpTestData()
- self.users.update({
- 'staff_blogpost_edit_own': create_user(
- username='staff_blogpost_edit_own',
- is_staff=True,
- user_permissions=('change_blogpost',),
- ),
- 'staff_blogpost_edit_all': create_user(
- username='staff_blogpost_edit_all',
- is_staff=True,
- user_permissions=('change_blogpost', 'edit_all_post'),
- ),
- })
+ self.users.update(
+ {
+ 'staff_blogpost_edit_own': create_user(
+ username='staff_blogpost_edit_own',
+ is_staff=True,
+ user_permissions=('change_blogpost',),
+ ),
+ 'staff_blogpost_edit_all': create_user(
+ username='staff_blogpost_edit_all',
+ is_staff=True,
+ user_permissions=('change_blogpost', 'edit_all_post'),
+ ),
+ }
+ )
self.basic_blogpost = create_blogpost(
title='basic',
diff --git a/judge/models/tests/test_contest.py b/judge/models/tests/test_contest.py
index 5be197e22b..37d445a8a0 100644
--- a/judge/models/tests/test_contest.py
+++ b/judge/models/tests/test_contest.py
@@ -4,56 +4,65 @@
from judge.models import Contest, ContestParticipation, ContestTag
from judge.models.contest import MinValueOrNoneValidator
-from judge.models.tests.util import CommonDataMixin, create_contest, create_contest_participation, create_user
+from judge.models.tests.util import (
+ CommonDataMixin,
+ create_contest,
+ create_contest_participation,
+ create_user,
+)
class ContestTestCase(CommonDataMixin, TestCase):
@classmethod
def setUpTestData(self):
super().setUpTestData()
- self.users.update({
- 'staff_contest_edit_own': create_user(
- username='staff_contest_edit_own',
- is_staff=True,
- user_permissions=('edit_own_contest',),
- ),
- 'staff_contest_see_all': create_user(
- username='staff_contest_see_all',
- user_permissions=('see_private_contest',),
- ),
- 'staff_contest_edit_all': create_user(
- username='staff_contest_edit_all',
- is_staff=True,
- user_permissions=('edit_own_contest', 'edit_all_contest'),
- ),
- 'normal_during_window': create_user(
- username='normal_during_window',
- ),
- 'normal_after_window': create_user(
- username='normal_after_window',
- ),
- 'normal_before_window': create_user(
- username='normal_before_window',
- ),
- 'non_staff_author': create_user(
- username='non_staff_author',
- is_staff=False,
- ),
- 'non_staff_tester': create_user(
- username='non_staff_tester',
- is_staff=False,
- ),
- 'normal_open_org': create_user(
- username='normal_open_org',
- is_staff=False,
- ),
- 'non_staff_spectator': create_user(
- username='non_staff_spectator',
- is_staff=False,
- ),
- })
-
- self.users['normal_open_org'].profile.organizations.add(self.organizations['open'])
+ self.users.update(
+ {
+ 'staff_contest_edit_own': create_user(
+ username='staff_contest_edit_own',
+ is_staff=True,
+ user_permissions=('edit_own_contest',),
+ ),
+ 'staff_contest_see_all': create_user(
+ username='staff_contest_see_all',
+ user_permissions=('see_private_contest',),
+ ),
+ 'staff_contest_edit_all': create_user(
+ username='staff_contest_edit_all',
+ is_staff=True,
+ user_permissions=('edit_own_contest', 'edit_all_contest'),
+ ),
+ 'normal_during_window': create_user(
+ username='normal_during_window',
+ ),
+ 'normal_after_window': create_user(
+ username='normal_after_window',
+ ),
+ 'normal_before_window': create_user(
+ username='normal_before_window',
+ ),
+ 'non_staff_author': create_user(
+ username='non_staff_author',
+ is_staff=False,
+ ),
+ 'non_staff_tester': create_user(
+ username='non_staff_tester',
+ is_staff=False,
+ ),
+ 'normal_open_org': create_user(
+ username='normal_open_org',
+ is_staff=False,
+ ),
+ 'non_staff_spectator': create_user(
+ username='non_staff_spectator',
+ is_staff=False,
+ ),
+ }
+ )
+
+ self.users['normal_open_org'].profile.organizations.add(
+ self.organizations['open']
+ )
_now = timezone.now()
@@ -170,7 +179,11 @@ def setUpTestData(self):
testers=('non_staff_tester',),
)
- for contest_key in ('contest_scoreboard', 'particip_scoreboard', 'visible_scoreboard'):
+ for contest_key in (
+ 'contest_scoreboard',
+ 'particip_scoreboard',
+ 'visible_scoreboard',
+ ):
create_contest_participation(
contest=contest_key,
user='normal_during_window',
@@ -276,7 +289,9 @@ def setUp(self):
def test_basic_contest(self):
self.assertTrue(self.basic_contest.show_scoreboard)
- self.assertEqual(self.basic_contest.contest_window_length, timezone.timedelta(days=101))
+ self.assertEqual(
+ self.basic_contest.contest_window_length, timezone.timedelta(days=101)
+ )
self.assertIsInstance(self.basic_contest._now, timezone.datetime)
self.assertTrue(self.basic_contest.started)
self.assertIsNone(self.basic_contest.time_before_start)
@@ -289,7 +304,9 @@ def test_hidden_scoreboard_contest(self):
self.assertFalse(self.hidden_scoreboard_contest.show_scoreboard)
for i in range(3):
with self.subTest(contest_problem_index=i):
- self.assertEqual(self.hidden_scoreboard_contest.get_label_for_problem(i), str(i))
+ self.assertEqual(
+ self.hidden_scoreboard_contest.get_label_for_problem(i), str(i)
+ )
self.assertEqual(self.hidden_scoreboard_contest.user_count, 1)
def test_private_contest(self):
@@ -302,14 +319,21 @@ def test_organization_private_contest(self):
self.assertTrue(self.organization_private_contest.show_scoreboard)
self.assertFalse(self.organization_private_contest.ended)
self.assertIsNone(self.organization_private_contest.time_before_start)
- self.assertIsInstance(self.organization_private_contest.time_before_end, timezone.timedelta)
+ self.assertIsInstance(
+ self.organization_private_contest.time_before_end, timezone.timedelta
+ )
def test_future_organization_private_contest(self):
self.assertFalse(self.future_organization_private_contest.started)
self.assertFalse(self.future_organization_private_contest.show_scoreboard)
self.assertFalse(self.future_organization_private_contest.ended)
- self.assertIsInstance(self.future_organization_private_contest.time_before_start, timezone.timedelta)
- self.assertIsInstance(self.future_organization_private_contest.time_before_end, timezone.timedelta)
+ self.assertIsInstance(
+ self.future_organization_private_contest.time_before_start,
+ timezone.timedelta,
+ )
+ self.assertIsInstance(
+ self.future_organization_private_contest.time_before_end, timezone.timedelta
+ )
def test_basic_contest_methods(self):
with self.assertRaises(Contest.Inaccessible):
@@ -447,7 +471,9 @@ def test_contest_hidden_scoreboard_non_staff_author_contest_methods(self):
'is_in_contest': self.assertFalse,
},
}
- self._test_object_methods_with_users(self.hidden_scoreboard_non_staff_author, data)
+ self._test_object_methods_with_users(
+ self.hidden_scoreboard_non_staff_author, data
+ )
def test_contest_hidden_scoreboard_contest_methods(self):
data = {
@@ -478,7 +504,9 @@ def test_contest_hidden_scoreboard_contest_methods(self):
'has_completed_contest': self.assertFalse,
},
}
- self._test_object_methods_with_users(self.contest_hidden_scoreboard_contest, data)
+ self._test_object_methods_with_users(
+ self.contest_hidden_scoreboard_contest, data
+ )
def test_particip_hidden_scoreboard_contest_methods(self):
data = {
@@ -508,7 +536,9 @@ def test_particip_hidden_scoreboard_contest_methods(self):
'has_completed_contest': self.assertFalse,
},
}
- self._test_object_methods_with_users(self.particip_hidden_scoreboard_contest, data)
+ self._test_object_methods_with_users(
+ self.particip_hidden_scoreboard_contest, data
+ )
def test_visible_scoreboard_contest_methods(self):
data = {
@@ -616,7 +646,9 @@ def test_public_limit_organization_join_contest(self):
'is_spectatable_by': self.assertFalse, # not in org
},
}
- self._test_object_methods_with_users(self.public_limit_organization_join_contest, data)
+ self._test_object_methods_with_users(
+ self.public_limit_organization_join_contest, data
+ )
def test_future_contest_methods(self):
data = {
@@ -651,7 +683,9 @@ def test_private_contest_methods(self):
# User must be in org and in private user list
with self.assertRaises(Contest.PrivateContest):
self.private_contest.access_check(self.users['normal_open_org'])
- self.private_contest.private_contestants.add(self.users['normal_open_org'].profile)
+ self.private_contest.private_contestants.add(
+ self.users['normal_open_org'].profile
+ )
with self.assertRaises(Contest.PrivateContest):
self.private_contest.access_check(self.users['normal_open_org'])
self.private_contest.organizations.add(self.organizations['open'])
@@ -799,7 +833,9 @@ def test_future_organization_private_contest_methods(self):
'is_in_contest': self.assertFalse,
},
}
- self._test_object_methods_with_users(self.future_organization_private_contest, data)
+ self._test_object_methods_with_users(
+ self.future_organization_private_contest, data
+ )
def test_private_user_contest_methods(self):
data = {
@@ -906,7 +942,9 @@ def test_contests_list(self):
with self.subTest(user=name):
# We only care about consistency between Contest.is_accessible_by and Contest.get_visible_contests
contest_keys = []
- for contest in Contest.objects.prefetch_related('testers', 'private_contestants', 'organizations'):
+ for contest in Contest.objects.prefetch_related(
+ 'testers', 'private_contestants', 'organizations'
+ ):
if contest.is_accessible_by(user):
contest_keys.append(contest.key)
diff --git a/judge/models/tests/test_problem.py b/judge/models/tests/test_problem.py
index 05b636955d..cfcc64fd8c 100644
--- a/judge/models/tests/test_problem.py
+++ b/judge/models/tests/test_problem.py
@@ -4,8 +4,16 @@
from judge.models import Language, LanguageLimit, Problem, Submission
from judge.models.problem import VotePermission, disallowed_characters_validator
-from judge.models.tests.util import CommonDataMixin, create_contest, create_contest_participation, \
- create_organization, create_problem, create_problem_type, create_solution, create_user
+from judge.models.tests.util import (
+ CommonDataMixin,
+ create_contest,
+ create_contest_participation,
+ create_organization,
+ create_problem,
+ create_problem_type,
+ create_solution,
+ create_user,
+)
class ProblemTestCase(CommonDataMixin, TestCase):
@@ -13,13 +21,15 @@ class ProblemTestCase(CommonDataMixin, TestCase):
def setUpTestData(self):
super().setUpTestData()
- self.users.update({
- 'staff_problem_edit_only_all': create_user(
- username='staff_problem_edit_only_all',
- is_staff=True,
- user_permissions=('edit_all_problem',),
- ),
- })
+ self.users.update(
+ {
+ 'staff_problem_edit_only_all': create_user(
+ username='staff_problem_edit_only_all',
+ is_staff=True,
+ user_permissions=('edit_all_problem',),
+ ),
+ }
+ )
create_problem_type(name='type')
@@ -32,7 +42,9 @@ def setUpTestData(self):
)
limits = []
- for lang in Language.objects.filter(common_name=Language.get_python3().common_name):
+ for lang in Language.objects.filter(
+ common_name=Language.get_python3().common_name
+ ):
limits.append(
LanguageLimit(
problem=self.basic_problem,
@@ -77,14 +89,23 @@ def test_basic_problem(self):
self.assertEqual(self.basic_problem.user_count, 0)
self.assertEqual(self.basic_problem.ac_rate, 0)
- self.assertListEqual(list(self.basic_problem.author_ids), [self.users['normal'].profile.id])
- self.assertListEqual(list(self.basic_problem.editor_ids), [self.users['normal'].profile.id])
- self.assertListEqual(list(self.basic_problem.tester_ids), [self.users['staff_problem_edit_public'].profile.id])
+ self.assertListEqual(
+ list(self.basic_problem.author_ids), [self.users['normal'].profile.id]
+ )
+ self.assertListEqual(
+ list(self.basic_problem.editor_ids), [self.users['normal'].profile.id]
+ )
+ self.assertListEqual(
+ list(self.basic_problem.tester_ids),
+ [self.users['staff_problem_edit_public'].profile.id],
+ )
self.assertListEqual(list(self.basic_problem.usable_languages), [])
self.assertListEqual(self.basic_problem.types_list, ['type'])
self.assertSetEqual(self.basic_problem.usable_common_names, set())
- self.assertEqual(self.basic_problem.translated_name('ABCDEFGHIJK'), self.basic_problem.name)
+ self.assertEqual(
+ self.basic_problem.translated_name('ABCDEFGHIJK'), self.basic_problem.name
+ )
self.assertFalse(self.basic_problem.clarifications.exists())
@@ -134,9 +155,13 @@ def test_basic_problem_methods(self):
self._test_object_methods_with_users(self.basic_problem, data)
def test_organization_private_problem_methods(self):
- self.assertFalse(self.organization_private_problem.is_accessible_by(self.users['normal']))
+ self.assertFalse(
+ self.organization_private_problem.is_accessible_by(self.users['normal'])
+ )
self.users['normal'].profile.organizations.add(self.organizations['open'])
- self.assertFalse(self.organization_private_problem.is_accessible_by(self.users['normal']))
+ self.assertFalse(
+ self.organization_private_problem.is_accessible_by(self.users['normal'])
+ )
self.organization_private_problem.organizations.add(self.organizations['open'])
data = {
@@ -218,7 +243,9 @@ def test_organization_admin_private_problem_methods(self):
'is_editable_by': self.assertFalse,
},
}
- self._test_object_methods_with_users(self.organization_admin_private_problem, data)
+ self._test_object_methods_with_users(
+ self.organization_admin_private_problem, data
+ )
def test_organization_admin_problem_methods(self):
data = {
@@ -255,7 +282,10 @@ def give_basic_problem_ac(self, user, points=None):
)
def test_problem_voting_permissions(self):
- self.assertEqual(self.basic_problem.vote_permission_for_user(self.users['anonymous']), VotePermission.NONE)
+ self.assertEqual(
+ self.basic_problem.vote_permission_for_user(self.users['anonymous']),
+ VotePermission.NONE,
+ )
now = timezone.now()
basic_contest = create_contest(
@@ -271,32 +301,52 @@ def test_problem_voting_permissions(self):
contest=basic_contest,
)
self.give_basic_problem_ac(in_contest)
- self.assertEqual(self.basic_problem.vote_permission_for_user(in_contest), VotePermission.NONE)
+ self.assertEqual(
+ self.basic_problem.vote_permission_for_user(in_contest), VotePermission.NONE
+ )
unlisted = create_user(username='unlisted')
unlisted.profile.is_unlisted = True
self.give_basic_problem_ac(unlisted)
- self.assertEqual(self.basic_problem.vote_permission_for_user(unlisted), VotePermission.VIEW)
+ self.assertEqual(
+ self.basic_problem.vote_permission_for_user(unlisted), VotePermission.VIEW
+ )
banned_from_voting = create_user(username='banned_from_voting')
banned_from_voting.profile.is_banned_from_problem_voting = True
self.give_basic_problem_ac(banned_from_voting)
- self.assertEqual(self.basic_problem.vote_permission_for_user(banned_from_voting), VotePermission.VIEW)
+ self.assertEqual(
+ self.basic_problem.vote_permission_for_user(banned_from_voting),
+ VotePermission.VIEW,
+ )
banned_from_problem = create_user(username='banned_from_problem')
self.basic_problem.banned_users.add(banned_from_problem.profile)
self.give_basic_problem_ac(banned_from_problem)
- self.assertEqual(self.basic_problem.vote_permission_for_user(banned_from_problem), VotePermission.VIEW)
+ self.assertEqual(
+ self.basic_problem.vote_permission_for_user(banned_from_problem),
+ VotePermission.VIEW,
+ )
- self.assertEqual(self.basic_problem.vote_permission_for_user(self.users['normal']), VotePermission.VIEW)
+ self.assertEqual(
+ self.basic_problem.vote_permission_for_user(self.users['normal']),
+ VotePermission.VIEW,
+ )
self.give_basic_problem_ac(self.users['normal'])
- self.assertEqual(self.basic_problem.vote_permission_for_user(self.users['normal']), VotePermission.VOTE)
+ self.assertEqual(
+ self.basic_problem.vote_permission_for_user(self.users['normal']),
+ VotePermission.VOTE,
+ )
partial_ac = create_user(username='partial_ac')
- self.give_basic_problem_ac(partial_ac, 0.5) # ensure this value is not equal to its point value
+ self.give_basic_problem_ac(
+ partial_ac, 0.5
+ ) # ensure this value is not equal to its point value
self.assertNotEqual(self.basic_problem.points, 0.5)
- self.assertEqual(self.basic_problem.vote_permission_for_user(partial_ac), VotePermission.VIEW)
+ self.assertEqual(
+ self.basic_problem.vote_permission_for_user(partial_ac), VotePermission.VIEW
+ )
def test_problems_list(self):
for name, user in self.users.items():
@@ -304,24 +354,32 @@ def test_problems_list(self):
with self.subTest(list='accessible problems'):
# We only care about consistency between Problem.is_accessible_by and Problem.get_visible_problems
problem_codes = []
- for problem in Problem.objects.prefetch_related('authors', 'curators', 'testers', 'organizations'):
+ for problem in Problem.objects.prefetch_related(
+ 'authors', 'curators', 'testers', 'organizations'
+ ):
if problem.is_accessible_by(user):
problem_codes.append(problem.code)
self.assertCountEqual(
- Problem.get_visible_problems(user).distinct().values_list('code', flat=True),
+ Problem.get_visible_problems(user)
+ .distinct()
+ .values_list('code', flat=True),
problem_codes,
)
with self.subTest(list='editable problems'):
# We only care about consistency between Problem.is_editable_by and Problem.get_editable_problems
problem_codes = []
- for problem in Problem.objects.prefetch_related('authors', 'curators'):
+ for problem in Problem.objects.prefetch_related(
+ 'authors', 'curators'
+ ):
if problem.is_editable_by(user):
problem_codes.append(problem.code)
self.assertCountEqual(
- Problem.get_editable_problems(user).distinct().values_list('code', flat=True),
+ Problem.get_editable_problems(user)
+ .distinct()
+ .values_list('code', flat=True),
problem_codes,
)
@@ -330,12 +388,14 @@ class SolutionTestCase(CommonDataMixin, TestCase):
@classmethod
def setUpTestData(self):
super().setUpTestData()
- self.users.update({
- 'staff_solution_see_all': create_user(
- username='staff_solution_see_all',
- user_permissions=('see_private_solution',),
- ),
- })
+ self.users.update(
+ {
+ 'staff_solution_see_all': create_user(
+ username='staff_solution_see_all',
+ user_permissions=('see_private_solution',),
+ ),
+ }
+ )
now = timezone.now()
@@ -434,7 +494,9 @@ def test_unpublished_solution_methods(self):
class DisallowedCharactersValidatorTestCase(SimpleTestCase):
def test_valid(self):
- with self.settings(DMOJ_PROBLEM_STATEMENT_DISALLOWED_CHARACTERS={'“', '”', '‘', '’'}):
+ with self.settings(
+ DMOJ_PROBLEM_STATEMENT_DISALLOWED_CHARACTERS={'“', '”', '‘', '’'}
+ ):
self.assertIsNone(disallowed_characters_validator(''))
self.assertIsNone(disallowed_characters_validator('"\'string\''))
@@ -443,8 +505,12 @@ def test_valid(self):
self.assertIsNone(disallowed_characters_validator('“”‘’'))
def test_invalid(self):
- with self.settings(DMOJ_PROBLEM_STATEMENT_DISALLOWED_CHARACTERS={'“', '”', '‘', '’'}):
+ with self.settings(
+ DMOJ_PROBLEM_STATEMENT_DISALLOWED_CHARACTERS={'“', '”', '‘', '’'}
+ ):
with self.assertRaises(ValidationError, msg='Disallowed characters: “'):
disallowed_characters_validator('“')
- with self.assertRaisesRegex(ValidationError, 'Disallowed characters: (?=.*‘)(?=.*’)'):
+ with self.assertRaisesRegex(
+ ValidationError, 'Disallowed characters: (?=.*‘)(?=.*’)'
+ ):
disallowed_characters_validator('‘’')
diff --git a/judge/models/tests/test_profile.py b/judge/models/tests/test_profile.py
index 91922b5a10..180f472b80 100644
--- a/judge/models/tests/test_profile.py
+++ b/judge/models/tests/test_profile.py
@@ -8,7 +8,11 @@
from django.utils.encoding import force_bytes
from judge.models import Profile
-from judge.models.tests.util import CommonDataMixin, create_contest, create_contest_participation
+from judge.models.tests.util import (
+ CommonDataMixin,
+ create_contest,
+ create_contest_participation,
+)
class OrganizationTestCase(CommonDataMixin, TestCase):
@@ -77,7 +81,11 @@ def test_generate_api_token(self):
self.assertTrue(
hmac.compare_digest(
- hmac.new(force_bytes(settings.SECRET_KEY), msg=force_bytes(raw_token), digestmod='sha256').hexdigest(),
+ hmac.new(
+ force_bytes(settings.SECRET_KEY),
+ msg=force_bytes(raw_token),
+ digestmod='sha256',
+ ).hexdigest(),
self.profile.api_token,
),
)
@@ -111,18 +119,26 @@ def test_css_class(self):
def test_get_user_css_class(self):
self.assertEqual(
- Profile.get_user_css_class(display_rank='abcdef', rating=None, rating_colors=True),
+ Profile.get_user_css_class(
+ display_rank='abcdef', rating=None, rating_colors=True
+ ),
'rating rate-none abcdef',
)
self.assertEqual(
- Profile.get_user_css_class(display_rank='admin', rating=1300, rating_colors=True),
+ Profile.get_user_css_class(
+ display_rank='admin', rating=1300, rating_colors=True
+ ),
'rating rate-expert admin',
)
self.assertEqual(
- Profile.get_user_css_class(display_rank=1111, rating=1299, rating_colors=True),
+ Profile.get_user_css_class(
+ display_rank=1111, rating=1299, rating_colors=True
+ ),
'rating rate-amateur 1111',
)
self.assertEqual(
- Profile.get_user_css_class(display_rank='random', rating=1299, rating_colors=False),
+ Profile.get_user_css_class(
+ display_rank='random', rating=1299, rating_colors=False
+ ),
'random',
)
diff --git a/judge/models/tests/test_submission.py b/judge/models/tests/test_submission.py
index 8274d52488..31db612db1 100644
--- a/judge/models/tests/test_submission.py
+++ b/judge/models/tests/test_submission.py
@@ -2,8 +2,14 @@
from django.utils import timezone
from judge.models import ContestSubmission, Language, Submission, SubmissionSource
-from judge.models.tests.util import CommonDataMixin, create_contest, create_contest_participation, \
- create_contest_problem, create_problem, create_user
+from judge.models.tests.util import (
+ CommonDataMixin,
+ create_contest,
+ create_contest_participation,
+ create_contest_problem,
+ create_problem,
+ create_user,
+)
class SubmissionTestCase(CommonDataMixin, TestCase):
@@ -11,13 +17,15 @@ class SubmissionTestCase(CommonDataMixin, TestCase):
def setUpTestData(self):
super().setUpTestData()
- self.users.update({
- 'staff_submission_view_all': create_user(
- username='staff_submission_view_all',
- is_staff=True,
- user_permissions=('view_all_submission',),
- ),
- })
+ self.users.update(
+ {
+ 'staff_submission_view_all': create_user(
+ username='staff_submission_view_all',
+ is_staff=True,
+ user_permissions=('view_all_submission',),
+ ),
+ }
+ )
self.basic_submission = Submission.objects.create(
user=self.users['normal'].profile,
@@ -92,8 +100,12 @@ def setUpTestData(self):
)
self.queued_contest_submission = ContestSubmission.objects.create(
submission=self.queued_submission,
- problem=create_contest_problem(problem=problem, contest=contest, partial=False),
- participation=create_contest_participation(contest=contest, user='superuser'),
+ problem=create_contest_problem(
+ problem=problem, contest=contest, partial=False
+ ),
+ participation=create_contest_participation(
+ contest=contest, user='superuser'
+ ),
)
def test_basic_submission(self):
diff --git a/judge/models/tests/util.py b/judge/models/tests/util.py
index 953308d7c3..86447542e9 100644
--- a/judge/models/tests/util.py
+++ b/judge/models/tests/util.py
@@ -1,8 +1,20 @@
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.utils import timezone
-from judge.models import BlogPost, Contest, ContestParticipation, ContestProblem, ContestTag, Language, Organization, \
- Problem, ProblemGroup, ProblemType, Profile, Solution
+from judge.models import (
+ BlogPost,
+ Contest,
+ ContestParticipation,
+ ContestProblem,
+ ContestTag,
+ Language,
+ Organization,
+ Problem,
+ ProblemGroup,
+ ProblemType,
+ Profile,
+ Solution,
+)
class CreateModel:
@@ -48,7 +60,11 @@ def __call__(self, *args, **kwargs):
if created:
for field, relationship in self.m2m_fields.items():
related_model, query_field = relationship
- getattr(obj, field).set(related_model.objects.filter(**{query_field + '__in': m2m_data[field]}))
+ getattr(obj, field).set(
+ related_model.objects.filter(
+ **{query_field + '__in': m2m_data[field]}
+ )
+ )
self.on_created_object(obj)
@@ -286,7 +302,11 @@ def setUpTestData(self):
'staff_problem_edit_all_with_rejudge': create_user(
username='staff_problem_edit_all_with_rejudge',
is_staff=True,
- user_permissions=('edit_own_problem', 'edit_all_problem', 'rejudge_submission'),
+ user_permissions=(
+ 'edit_own_problem',
+ 'edit_all_problem',
+ 'rejudge_submission',
+ ),
),
'staff_problem_edit_own_no_staff': create_user(
username='staff_problem_edit_own_no_staff',
@@ -317,5 +337,6 @@ def _test_object_methods_with_users(self, obj, data):
with self.subTest(method=method):
func(
getattr(obj, method)(self.users[username]),
- msg='Method "%s" failed for user "%s", object "%s".' % (method, username, obj),
+ msg='Method "%s" failed for user "%s", object "%s".'
+ % (method, username, obj),
)
diff --git a/judge/models/ticket.py b/judge/models/ticket.py
index 7203f759a3..2ceae52092 100644
--- a/judge/models/ticket.py
+++ b/judge/models/ticket.py
@@ -8,15 +8,27 @@
class Ticket(models.Model):
title = models.CharField(max_length=100, verbose_name=_('ticket title'))
- user = models.ForeignKey(Profile, verbose_name=_('ticket creator'), related_name='tickets',
- on_delete=models.CASCADE)
+ user = models.ForeignKey(
+ Profile,
+ verbose_name=_('ticket creator'),
+ related_name='tickets',
+ on_delete=models.CASCADE,
+ )
time = models.DateTimeField(verbose_name=_('creation time'), auto_now_add=True)
- assignees = models.ManyToManyField(Profile, verbose_name=_('assignees'), related_name='assigned_tickets',
- blank=True)
- notes = models.TextField(verbose_name=_('quick notes'), blank=True,
- help_text=_('Staff notes for this issue to aid in processing.'))
- content_type = models.ForeignKey(ContentType, verbose_name=_('linked item type'),
- on_delete=models.CASCADE)
+ assignees = models.ManyToManyField(
+ Profile,
+ verbose_name=_('assignees'),
+ related_name='assigned_tickets',
+ blank=True,
+ )
+ notes = models.TextField(
+ verbose_name=_('quick notes'),
+ blank=True,
+ help_text=_('Staff notes for this issue to aid in processing.'),
+ )
+ content_type = models.ForeignKey(
+ ContentType, verbose_name=_('linked item type'), on_delete=models.CASCADE
+ )
object_id = models.PositiveIntegerField(verbose_name=_('linked item ID'))
linked_item = GenericForeignKey()
is_open = models.BooleanField(verbose_name=_('is ticket open?'), default=True)
@@ -27,10 +39,19 @@ class Meta:
class TicketMessage(models.Model):
- ticket = models.ForeignKey(Ticket, verbose_name=_('ticket'), related_name='messages',
- related_query_name='message', on_delete=models.CASCADE)
- user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='ticket_messages',
- on_delete=models.CASCADE)
+ ticket = models.ForeignKey(
+ Ticket,
+ verbose_name=_('ticket'),
+ related_name='messages',
+ related_query_name='message',
+ on_delete=models.CASCADE,
+ )
+ user = models.ForeignKey(
+ Profile,
+ verbose_name=_('user'),
+ related_name='ticket_messages',
+ on_delete=models.CASCADE,
+ )
body = models.TextField(verbose_name=_('message body'))
time = models.DateTimeField(verbose_name=_('message time'), auto_now_add=True)
diff --git a/judge/performance_points.py b/judge/performance_points.py
index 30de63e19d..b37d684ec7 100644
--- a/judge/performance_points.py
+++ b/judge/performance_points.py
@@ -6,18 +6,24 @@
from judge.models import Submission
from judge.timezone import from_database_time
-PP_WEIGHT_TABLE = [pow(settings.DMOJ_PP_STEP, i) for i in range(settings.DMOJ_PP_ENTRIES)]
+PP_WEIGHT_TABLE = [
+ pow(settings.DMOJ_PP_STEP, i) for i in range(settings.DMOJ_PP_ENTRIES)
+]
-PPBreakdown = namedtuple('PPBreakdown', 'points weight scaled_points problem_name problem_code '
- 'sub_id sub_date sub_points sub_total sub_result_class '
- 'sub_short_status sub_long_status sub_lang')
+PPBreakdown = namedtuple(
+ 'PPBreakdown',
+ 'points weight scaled_points problem_name problem_code '
+ 'sub_id sub_date sub_points sub_total sub_result_class '
+ 'sub_short_status sub_long_status sub_lang',
+)
def get_pp_breakdown(user, start=0, end=settings.DMOJ_PP_ENTRIES):
join_type = 'STRAIGHT_JOIN' if connection.vendor == 'mysql' else 'INNER JOIN'
with connection.cursor() as cursor:
- cursor.execute(f"""
+ cursor.execute(
+ f"""
SELECT max_points_table.problem_code,
max_points_table.problem_name,
max_points_table.max_points,
@@ -51,32 +57,49 @@ def get_pp_breakdown(user, start=0, end=settings.DMOJ_PP_ENTRIES):
GROUP BY max_points_table.problem_id
ORDER BY max_points DESC, judge_submission.date DESC
LIMIT %s OFFSET %s
- """, (user.id, user.id, end - start + 1, start))
+ """,
+ (user.id, user.id, end - start + 1, start),
+ )
data = cursor.fetchall()
breakdown = []
for weight, contrib in zip(PP_WEIGHT_TABLE[start:end], data):
- code, name, points, id, date, case_points, case_total, result, lang_short_name, lang_key = contrib
+ (
+ code,
+ name,
+ points,
+ id,
+ date,
+ case_points,
+ case_total,
+ result,
+ lang_short_name,
+ lang_key,
+ ) = contrib
# Replicates a lot of the logic usually done on Submission objects
lang_short_display_name = lang_short_name or lang_key
- result_class = Submission.result_class_from_code(result, case_points, case_total)
+ result_class = Submission.result_class_from_code(
+ result, case_points, case_total
+ )
long_status = Submission.USER_DISPLAY_CODES.get(result, '')
- breakdown.append(PPBreakdown(
- points=points,
- weight=weight * 100,
- scaled_points=points * weight,
- problem_name=name,
- problem_code=code,
- sub_id=id,
- sub_date=from_database_time(date),
- sub_points=case_points,
- sub_total=case_total,
- sub_short_status=result,
- sub_long_status=long_status,
- sub_result_class=result_class,
- sub_lang=lang_short_display_name,
- ))
+ breakdown.append(
+ PPBreakdown(
+ points=points,
+ weight=weight * 100,
+ scaled_points=points * weight,
+ problem_name=name,
+ problem_code=code,
+ sub_id=id,
+ sub_date=from_database_time(date),
+ sub_points=case_points,
+ sub_total=case_total,
+ sub_short_status=result,
+ sub_long_status=long_status,
+ sub_result_class=result_class,
+ sub_lang=lang_short_display_name,
+ )
+ )
has_more = end < min(len(PP_WEIGHT_TABLE), start + len(data))
return breakdown, has_more
diff --git a/judge/ratings.py b/judge/ratings.py
index 05e8d1d59b..39a91c465d 100644
--- a/judge/ratings.py
+++ b/judge/ratings.py
@@ -9,14 +9,16 @@
from django.utils.translation import gettext_lazy
-BETA2 = 328.33 ** 2
-RATING_INIT = 1200 # Newcomer's rating when applying the rating floor/ceiling
-MEAN_INIT = 1500.
+BETA2 = 328.33**2
+RATING_INIT = 1200 # Newcomer's rating when applying the rating floor/ceiling
+MEAN_INIT = 1500.0
VAR_INIT = 350**2 * (BETA2 / 212**2)
SD_INIT = sqrt(VAR_INIT)
VALID_RANGE = MEAN_INIT - 20 * SD_INIT, MEAN_INIT + 20 * SD_INIT
VAR_PER_CONTEST = 1219.047619 * (BETA2 / 212**2)
-VAR_LIM = (sqrt(VAR_PER_CONTEST**2 + 4 * BETA2 * VAR_PER_CONTEST) - VAR_PER_CONTEST) / 2
+VAR_LIM = (
+ sqrt(VAR_PER_CONTEST**2 + 4 * BETA2 * VAR_PER_CONTEST) - VAR_PER_CONTEST
+) / 2
SD_LIM = sqrt(VAR_LIM)
TANH_C = sqrt(3) / pi
@@ -72,15 +74,15 @@ def solve(tanh_terms, y_tg, lin_factor=0, bounds=VALID_RANGE):
def get_var(times_ranked, cache=[VAR_INIT]):
while times_ranked >= len(cache):
- next_var = 1. / (1. / (cache[-1] + VAR_PER_CONTEST) + 1. / BETA2)
+ next_var = 1.0 / (1.0 / (cache[-1] + VAR_PER_CONTEST) + 1.0 / BETA2)
cache.append(next_var)
return cache[times_ranked]
def recalculate_ratings(ranking, old_mean, times_ranked, historical_p):
n = len(ranking)
- new_p = [0.] * n
- new_mean = [0.] * n
+ new_p = [0.0] * n
+ new_mean = [0.0] * n
# Note: pre-multiply delta by TANH_C to improve efficiency.
delta = [TANH_C * sqrt(get_var(t) + VAR_PER_CONTEST + BETA2) for t in times_ranked]
@@ -91,10 +93,10 @@ def solve_idx(i, bounds=VALID_RANGE):
r = ranking[i]
y_tg = 0
for d, s in zip(delta, ranking):
- if s > r: # s loses to r
- y_tg += 1. / d
- elif s < r: # s beats r
- y_tg -= 1. / d
+ if s > r: # s loses to r
+ y_tg += 1.0 / d
+ elif s < r: # s beats r
+ y_tg -= 1.0 / d
# Otherwise, this is a tie that counts as half a win, as per Elo-MMR.
new_p[i] = solve(p_tanh_terms, y_tg, bounds=bounds)
@@ -118,10 +120,10 @@ def divconq(i, j):
# Calculate mean.
for i, r in enumerate(ranking):
tanh_terms = []
- w_prev = 1.
- w_sum = 0.
+ w_prev = 1.0
+ w_sum = 0.0
for j, h in enumerate([new_p[i]] + historical_p[i]):
- gamma2 = (VAR_PER_CONTEST if j > 0 else 0)
+ gamma2 = VAR_PER_CONTEST if j > 0 else 0
h_var = get_var(times_ranked[i] + 1 - j)
k = h_var / (h_var + gamma2)
w = w_prev * k**2
@@ -129,13 +131,16 @@ def divconq(i, j):
tanh_terms.append((h, sqrt(BETA2) * TANH_C, w))
w_prev = w
w_sum += w / BETA2
- w0 = 1. / get_var(times_ranked[i] + 1) - w_sum
+ w0 = 1.0 / get_var(times_ranked[i] + 1) - w_sum
p0 = eval_tanhs(tanh_terms[1:], old_mean[i]) / w0 + old_mean[i]
new_mean[i] = solve(tanh_terms, w0 * p0, lin_factor=w0)
# Display a slightly lower rating to incentivize participation.
# As times_ranked increases, new_rating converges to new_mean.
- new_rating = [max(1, round(m - (sqrt(get_var(t + 1)) - SD_LIM))) for m, t in zip(new_mean, times_ranked)]
+ new_rating = [
+ max(1, round(m - (sqrt(get_var(t + 1)) - SD_LIM)))
+ for m, t in zip(new_mean, times_ranked)
+ ]
return new_rating, new_mean, new_p
@@ -145,15 +150,37 @@ def rate_contest(contest):
rating_subquery = Rating.objects.filter(user=OuterRef('user'))
rating_sorted = rating_subquery.order_by('-contest__end_time')
- users = contest.users.order_by('is_disqualified', '-score', 'cumtime', 'tiebreaker') \
- .annotate(submissions=Count('submission'),
- last_rating=Coalesce(Subquery(rating_sorted.values('rating')[:1]), RATING_INIT),
- last_mean=Coalesce(Subquery(rating_sorted.values('mean')[:1]), MEAN_INIT),
- times=Coalesce(Subquery(rating_subquery.order_by().values('user_id')
- .annotate(count=Count('id')).values('count')), 0)) \
- .exclude(user_id__in=contest.rate_exclude.all()) \
- .filter(virtual=0).values('id', 'user_id', 'score', 'cumtime', 'tiebreaker',
- 'last_rating', 'last_mean', 'times')
+ users = (
+ contest.users.order_by('is_disqualified', '-score', 'cumtime', 'tiebreaker')
+ .annotate(
+ submissions=Count('submission'),
+ last_rating=Coalesce(
+ Subquery(rating_sorted.values('rating')[:1]), RATING_INIT
+ ),
+ last_mean=Coalesce(Subquery(rating_sorted.values('mean')[:1]), MEAN_INIT),
+ times=Coalesce(
+ Subquery(
+ rating_subquery.order_by()
+ .values('user_id')
+ .annotate(count=Count('id'))
+ .values('count')
+ ),
+ 0,
+ ),
+ )
+ .exclude(user_id__in=contest.rate_exclude.all())
+ .filter(virtual=0)
+ .values(
+ 'id',
+ 'user_id',
+ 'score',
+ 'cumtime',
+ 'tiebreaker',
+ 'last_rating',
+ 'last_mean',
+ 'times',
+ )
+ )
if not contest.rate_all:
users = users.filter(submissions__gt=0)
if contest.rating_floor is not None:
@@ -170,24 +197,46 @@ def rate_contest(contest):
historical_p = [[] for _ in users]
user_id_to_idx = {uid: i for i, uid in enumerate(user_ids)}
- for h in Rating.objects.filter(user_id__in=user_ids) \
- .order_by('-contest__end_time') \
- .values('user_id', 'performance'):
+ for h in (
+ Rating.objects.filter(user_id__in=user_ids)
+ .order_by('-contest__end_time')
+ .values('user_id', 'performance')
+ ):
idx = user_id_to_idx[h['user_id']]
historical_p[idx].append(h['performance'])
- rating, mean, performance = recalculate_ratings(ranking, old_mean, times_ranked, historical_p)
+ rating, mean, performance = recalculate_ratings(
+ ranking, old_mean, times_ranked, historical_p
+ )
now = timezone.now()
- ratings = [Rating(user_id=i, contest=contest, rating=r, mean=m, performance=perf,
- last_rated=now, participation_id=pid, rank=z)
- for i, pid, r, m, perf, z in zip(user_ids, participation_ids, rating, mean, performance, ranking)]
+ ratings = [
+ Rating(
+ user_id=i,
+ contest=contest,
+ rating=r,
+ mean=m,
+ performance=perf,
+ last_rated=now,
+ participation_id=pid,
+ rank=z,
+ )
+ for i, pid, r, m, perf, z in zip(
+ user_ids, participation_ids, rating, mean, performance, ranking
+ )
+ ]
with transaction.atomic():
Rating.objects.bulk_create(ratings)
- Profile.objects.filter(contest_history__contest=contest, contest_history__virtual=0).update(
- rating=Subquery(Rating.objects.filter(user=OuterRef('id'))
- .order_by('-contest__end_time').values('rating')[:1]))
+ Profile.objects.filter(
+ contest_history__contest=contest, contest_history__virtual=0
+ ).update(
+ rating=Subquery(
+ Rating.objects.filter(user=OuterRef('id'))
+ .order_by('-contest__end_time')
+ .values('rating')[:1]
+ )
+ )
RATING_LEVELS = [
@@ -200,8 +249,15 @@ def rate_contest(contest):
gettext_lazy('Target'),
]
RATING_VALUES = [1000, 1300, 1600, 1900, 2400, 3000]
-RATING_CLASS = ['rate-newbie', 'rate-amateur', 'rate-expert', 'rate-candidate-master',
- 'rate-master', 'rate-grandmaster', 'rate-target']
+RATING_CLASS = [
+ 'rate-newbie',
+ 'rate-amateur',
+ 'rate-expert',
+ 'rate-candidate-master',
+ 'rate-master',
+ 'rate-grandmaster',
+ 'rate-target',
+]
def rating_level(rating):
diff --git a/judge/signals.py b/judge/signals.py
index ce4c5d4612..53013cba21 100644
--- a/judge/signals.py
+++ b/judge/signals.py
@@ -9,8 +9,22 @@
from django.dispatch import receiver
from .caching import finished_submission
-from .models import BlogPost, Comment, Contest, ContestSubmission, EFFECTIVE_MATH_ENGINES, Judge, Language, License, \
- MiscConfig, Organization, Problem, Profile, Submission, WebAuthnCredential
+from .models import (
+ BlogPost,
+ Comment,
+ Contest,
+ ContestSubmission,
+ EFFECTIVE_MATH_ENGINES,
+ Judge,
+ Language,
+ License,
+ MiscConfig,
+ Organization,
+ Problem,
+ Profile,
+ Submission,
+ WebAuthnCredential,
+)
def get_pdf_path(basename: str) -> Optional[str]:
@@ -33,16 +47,33 @@ def problem_update(sender, instance, **kwargs):
if hasattr(instance, '_updating_stats_only'):
return
- cache.delete_many([
- make_template_fragment_key('submission_problem', (instance.id,)),
- make_template_fragment_key('problem_feed', (instance.id,)),
- 'problem_tls:%s' % instance.id, 'problem_mls:%s' % instance.id,
- ])
- cache.delete_many([make_template_fragment_key('problem_html', (instance.id, engine, lang))
- for lang, _ in settings.LANGUAGES for engine in EFFECTIVE_MATH_ENGINES])
- cache.delete_many([make_template_fragment_key('problem_authors', (instance.id, lang))
- for lang, _ in settings.LANGUAGES])
- cache.delete_many(['generated-meta-problem:%s:%d' % (lang, instance.id) for lang, _ in settings.LANGUAGES])
+ cache.delete_many(
+ [
+ make_template_fragment_key('submission_problem', (instance.id,)),
+ make_template_fragment_key('problem_feed', (instance.id,)),
+ 'problem_tls:%s' % instance.id,
+ 'problem_mls:%s' % instance.id,
+ ]
+ )
+ cache.delete_many(
+ [
+ make_template_fragment_key('problem_html', (instance.id, engine, lang))
+ for lang, _ in settings.LANGUAGES
+ for engine in EFFECTIVE_MATH_ENGINES
+ ]
+ )
+ cache.delete_many(
+ [
+ make_template_fragment_key('problem_authors', (instance.id, lang))
+ for lang, _ in settings.LANGUAGES
+ ]
+ )
+ cache.delete_many(
+ [
+ 'generated-meta-problem:%s:%d' % (lang, instance.id)
+ for lang, _ in settings.LANGUAGES
+ ]
+ )
for lang, _ in settings.LANGUAGES:
cached_pdf_filename = get_pdf_path('%s.%s.pdf' % (instance.code, lang))
@@ -55,10 +86,16 @@ def profile_update(sender, instance, **kwargs):
if hasattr(instance, '_updating_stats_only'):
return
- cache.delete_many([make_template_fragment_key('user_about', (instance.id, engine))
- for engine in EFFECTIVE_MATH_ENGINES] +
- [make_template_fragment_key('org_member_count', (org_id,))
- for org_id in instance.organizations.values_list('id', flat=True)])
+ cache.delete_many(
+ [
+ make_template_fragment_key('user_about', (instance.id, engine))
+ for engine in EFFECTIVE_MATH_ENGINES
+ ]
+ + [
+ make_template_fragment_key('org_member_count', (org_id,))
+ for org_id in instance.organizations.values_list('id', flat=True)
+ ]
+ )
@receiver(post_delete, sender=WebAuthnCredential)
@@ -74,9 +111,13 @@ def contest_update(sender, instance, **kwargs):
if hasattr(instance, '_updating_stats_only'):
return
- cache.delete_many(['generated-meta-contest:%d' % instance.id] +
- [make_template_fragment_key('contest_html', (instance.id, engine))
- for engine in EFFECTIVE_MATH_ENGINES])
+ cache.delete_many(
+ ['generated-meta-contest:%d' % instance.id]
+ + [
+ make_template_fragment_key('contest_html', (instance.id, engine))
+ for engine in EFFECTIVE_MATH_ENGINES
+ ]
+ )
@receiver(post_save, sender=License)
@@ -86,8 +127,9 @@ def license_update(sender, instance, **kwargs):
@receiver(post_save, sender=Language)
def language_update(sender, instance, **kwargs):
- cache.delete_many([make_template_fragment_key('language_html', (instance.id,)),
- 'lang:cn_map'])
+ cache.delete_many(
+ [make_template_fragment_key('language_html', (instance.id,)), 'lang:cn_map']
+ )
@receiver(post_save, sender=Judge)
@@ -102,13 +144,19 @@ def comment_update(sender, instance, **kwargs):
@receiver(post_save, sender=BlogPost)
def post_update(sender, instance, **kwargs):
- cache.delete_many([
- make_template_fragment_key('post_summary', (instance.id,)),
- 'blog_slug:%d' % instance.id,
- 'blog_feed:%d' % instance.id,
- ])
- cache.delete_many([make_template_fragment_key('post_content', (instance.id, engine))
- for engine in EFFECTIVE_MATH_ENGINES])
+ cache.delete_many(
+ [
+ make_template_fragment_key('post_summary', (instance.id,)),
+ 'blog_slug:%d' % instance.id,
+ 'blog_feed:%d' % instance.id,
+ ]
+ )
+ cache.delete_many(
+ [
+ make_template_fragment_key('post_content', (instance.id, engine))
+ for engine in EFFECTIVE_MATH_ENGINES
+ ]
+ )
@receiver(post_delete, sender=Submission)
@@ -129,8 +177,12 @@ def contest_submission_delete(sender, instance, **kwargs):
@receiver(post_save, sender=Organization)
def organization_update(sender, instance, **kwargs):
- cache.delete_many([make_template_fragment_key('organization_html', (instance.id, engine))
- for engine in EFFECTIVE_MATH_ENGINES])
+ cache.delete_many(
+ [
+ make_template_fragment_key('organization_html', (instance.id, engine))
+ for engine in EFFECTIVE_MATH_ENGINES
+ ]
+ )
@receiver(post_save, sender=MiscConfig)
@@ -145,4 +197,6 @@ def misc_config_delete(sender, instance, **kwargs):
@receiver(post_save, sender=ContestSubmission)
def contest_submission_update(sender, instance, **kwargs):
- Submission.objects.filter(id=instance.submission_id).update(contest_object_id=instance.participation.contest_id)
+ Submission.objects.filter(id=instance.submission_id).update(
+ contest_object_id=instance.participation.contest_id
+ )
diff --git a/judge/sitemap.py b/judge/sitemap.py
index 5c3426ad29..10f5ee7c73 100644
--- a/judge/sitemap.py
+++ b/judge/sitemap.py
@@ -33,8 +33,9 @@ class ContestSitemap(Sitemap):
priority = 0.7
def items(self):
- return Contest.objects.filter(is_visible=True, is_private=False,
- is_organization_private=False).values_list('key')
+ return Contest.objects.filter(
+ is_visible=True, is_private=False, is_organization_private=False
+ ).values_list('key')
def location(self, obj):
return reverse('contest_view', args=obj)
@@ -56,7 +57,9 @@ class BlogPostSitemap(Sitemap):
priority = 0.7
def items(self):
- return BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now()).values_list('id', 'slug')
+ return BlogPost.objects.filter(
+ visible=True, publish_on__lte=timezone.now()
+ ).values_list('id', 'slug')
def location(self, obj):
return reverse('blog_post', args=obj)
@@ -67,8 +70,11 @@ class SolutionSitemap(Sitemap):
priority = 0.8
def items(self):
- return (Solution.objects.filter(is_public=True, publish_on__lte=timezone.now(),
- problem__in=Problem.get_public_problems()).values_list('problem__code'))
+ return Solution.objects.filter(
+ is_public=True,
+ publish_on__lte=timezone.now(),
+ problem__in=Problem.get_public_problems(),
+ ).values_list('problem__code')
def location(self, obj):
return reverse('problem_editorial', args=obj)
@@ -104,9 +110,11 @@ def changefreq(self, obj):
sitemaps = {
'home': HomePageSitemap,
- 'pages': UrlSitemap([
- {'location': '/about/', 'priority': 0.9},
- ]),
+ 'pages': UrlSitemap(
+ [
+ {'location': '/about/', 'priority': 0.9},
+ ]
+ ),
'problem': ProblemSitemap,
'solutions': SolutionSitemap,
'blog': BlogPostSitemap,
diff --git a/judge/social_auth.py b/judge/social_auth.py
index f8fcc2453e..8df6f723c5 100644
--- a/judge/social_auth.py
+++ b/judge/social_auth.py
@@ -13,7 +13,9 @@
from social_core.backends.github import GithubOAuth2
from social_core.exceptions import InvalidEmail, SocialAuthBaseException
from social_core.pipeline.partial import partial
-from social_django.middleware import SocialAuthExceptionMiddleware as OldSocialAuthExceptionMiddleware
+from social_django.middleware import (
+ SocialAuthExceptionMiddleware as OldSocialAuthExceptionMiddleware,
+)
from judge.forms import ProfileForm
from judge.models import Language, Profile
@@ -31,7 +33,11 @@ def user_data(self, access_token, *args, **kwargs):
except (HTTPError, ValueError, TypeError):
emails = []
- emails = [(e.get('email'), e.get('primary'), 0) for e in emails if isinstance(e, dict) and e.get('verified')]
+ emails = [
+ (e.get('email'), e.get('primary'), 0)
+ for e in emails
+ if isinstance(e, dict) and e.get('verified')
+ ]
emails.sort(key=itemgetter(1), reverse=True)
emails = list(map(itemgetter(0), emails))
@@ -53,8 +59,14 @@ def verify_email(backend, details, *args, **kwargs):
class UsernameForm(forms.Form):
- username = forms.RegexField(regex=r'^\w+$', max_length=30, label='Username',
- error_messages={'invalid': 'A username must contain letters, numbers, or underscores.'})
+ username = forms.RegexField(
+ regex=r'^\w+$',
+ max_length=30,
+ label='Username',
+ error_messages={
+ 'invalid': 'A username must contain letters, numbers, or underscores.'
+ },
+ )
def clean_username(self):
if User.objects.filter(username=self.cleaned_data['username']).exists():
@@ -72,9 +84,14 @@ def choose_username(backend, user, username=None, *args, **kwargs):
return {'username': form.cleaned_data['username']}
else:
form = UsernameForm(initial={'username': username})
- return render(request, 'registration/username_select.html', {
- 'title': 'Choose a username', 'form': form,
- })
+ return render(
+ request,
+ 'registration/username_select.html',
+ {
+ 'title': 'Choose a username',
+ 'form': form,
+ },
+ )
@partial
@@ -96,13 +113,23 @@ def make_profile(backend, user, response, is_new=False, *args, **kwargs):
revisions.set_user(user)
revisions.set_comment('Updated on registration')
return
- return render(backend.strategy.request, 'registration/profile_creation.html', {
- 'title': 'Create your profile', 'form': form,
- })
+ return render(
+ backend.strategy.request,
+ 'registration/profile_creation.html',
+ {
+ 'title': 'Create your profile',
+ 'form': form,
+ },
+ )
class SocialAuthExceptionMiddleware(OldSocialAuthExceptionMiddleware):
def process_exception(self, request, exception):
if isinstance(exception, SocialAuthBaseException):
- return HttpResponseRedirect('%s?message=%s' % (reverse('social_auth_error'),
- quote(self.get_message(request, exception))))
+ return HttpResponseRedirect(
+ '%s?message=%s'
+ % (
+ reverse('social_auth_error'),
+ quote(self.get_message(request, exception)),
+ )
+ )
diff --git a/judge/tasks/contest.py b/judge/tasks/contest.py
index 0c36e23645..2bb882ec30 100644
--- a/judge/tasks/contest.py
+++ b/judge/tasks/contest.py
@@ -16,7 +16,9 @@ def rescore_contest(self, contest_key):
participations = contest.users
rescored = 0
- with Progress(self, participations.count(), stage=_('Recalculating contest scores')) as p:
+ with Progress(
+ self, participations.count(), stage=_('Recalculating contest scores')
+ ) as p:
for participation in participations.iterator():
participation.recompute_results()
rescored += 1
@@ -40,18 +42,31 @@ def run_moss(self, contest_key):
with Progress(self, length, stage=_('Running MOSS')) as p:
for problem in contest.problems.all():
for dmoj_lang, moss_lang in ContestMoss.LANG_MAPPING:
- result = ContestMoss(contest=contest, problem=problem, language=dmoj_lang)
-
- subs = Submission.objects.filter(
- contest__participation__virtual__in=(ContestParticipation.LIVE, ContestParticipation.SPECTATE),
- contest_object=contest,
- problem=problem,
- language__common_name=dmoj_lang,
- ).order_by('-points').values_list('user__user__username', 'source__source')
+ result = ContestMoss(
+ contest=contest, problem=problem, language=dmoj_lang
+ )
+
+ subs = (
+ Submission.objects.filter(
+ contest__participation__virtual__in=(
+ ContestParticipation.LIVE,
+ ContestParticipation.SPECTATE,
+ ),
+ contest_object=contest,
+ problem=problem,
+ language__common_name=dmoj_lang,
+ )
+ .order_by('-points')
+ .values_list('user__user__username', 'source__source')
+ )
if subs.exists():
- moss_call = MOSS(moss_api_key, language=moss_lang, matching_file_limit=100,
- comment='%s - %s' % (contest.key, problem.code))
+ moss_call = MOSS(
+ moss_api_key,
+ language=moss_lang,
+ matching_file_limit=100,
+ comment='%s - %s' % (contest.key, problem.code),
+ )
users = set()
diff --git a/judge/tasks/submission.py b/judge/tasks/submission.py
index a190ba71ee..b916c6ae6c 100644
--- a/judge/tasks/submission.py
+++ b/judge/tasks/submission.py
@@ -18,13 +18,16 @@ def apply_submission_filter(queryset, id_range, languages, results):
queryset = queryset.filter(language_id__in=languages)
if results:
queryset = queryset.filter(result__in=results)
- queryset = queryset.exclude(locked_after__lt=timezone.now()) \
- .exclude(status__in=Submission.IN_PROGRESS_GRADING_STATUS)
+ queryset = queryset.exclude(locked_after__lt=timezone.now()).exclude(
+ status__in=Submission.IN_PROGRESS_GRADING_STATUS
+ )
return queryset
@shared_task(bind=True)
-def rejudge_problem_filter(self, problem_id, id_range=None, languages=None, results=None, user_id=None):
+def rejudge_problem_filter(
+ self, problem_id, id_range=None, languages=None, results=None, user_id=None
+):
queryset = Submission.objects.filter(problem_id=problem_id)
queryset = apply_submission_filter(queryset, id_range, languages, results)
user = User.objects.get(id=user_id)
@@ -47,8 +50,12 @@ def rescore_problem(self, problem_id):
with Progress(self, submissions.count(), stage=_('Modifying submissions')) as p:
rescored = 0
for submission in submissions.iterator():
- submission.points = round(submission.case_points / submission.case_total * problem.points
- if submission.case_total else 0, 1)
+ submission.points = round(
+ submission.case_points / submission.case_total * problem.points
+ if submission.case_total
+ else 0,
+ 1,
+ )
if not problem.partial and submission.points < problem.points:
submission.points = 0
submission.save(update_fields=['points'])
@@ -57,9 +64,15 @@ def rescore_problem(self, problem_id):
if rescored % 10 == 0:
p.done = rescored
- with Progress(self, submissions.values('user_id').distinct().count(), stage=_('Recalculating user points')) as p:
+ with Progress(
+ self,
+ submissions.values('user_id').distinct().count(),
+ stage=_('Recalculating user points'),
+ ) as p:
users = 0
- profiles = Profile.objects.filter(id__in=submissions.values_list('user_id', flat=True).distinct())
+ profiles = Profile.objects.filter(
+ id__in=submissions.values_list('user_id', flat=True).distinct()
+ )
for profile in profiles.iterator():
profile._updating_stats_only = True
profile.calculate_points()
diff --git a/judge/tasks/user.py b/judge/tasks/user.py
index 4fa6304add..35b6ea9fcf 100644
--- a/judge/tasks/user.py
+++ b/judge/tasks/user.py
@@ -31,7 +31,9 @@ def apply_submission_filter(queryset, options):
problem_glob = rewildcard.sub('*', options['submission_problem_glob'])
if problem_glob != '*':
queryset = queryset.filter(
- problem__in=Problem.objects.filter(code__regex=fnmatch.translate(problem_glob)),
+ problem__in=Problem.objects.filter(
+ code__regex=fnmatch.translate(problem_glob)
+ ),
)
return list(queryset)
@@ -50,18 +52,26 @@ def prepare_user_data(self, profile_id, options):
# Force an update so that we get a progress bar.
p.done = 0
submissions = apply_submission_filter(
- Submission.objects.select_related('problem', 'language', 'source').filter(user_id=profile_id),
+ Submission.objects.select_related('problem', 'language', 'source').filter(
+ user_id=profile_id
+ ),
options,
)
p.did(1)
- comments = apply_comment_filter(Comment.objects.filter(author_id=profile_id), options)
+ comments = apply_comment_filter(
+ Comment.objects.filter(author_id=profile_id), options
+ )
p.did(1)
- with zipfile.ZipFile(os.path.join(settings.DMOJ_USER_DATA_CACHE, '%s.zip' % profile_id), mode='w') as data_file:
+ with zipfile.ZipFile(
+ os.path.join(settings.DMOJ_USER_DATA_CACHE, '%s.zip' % profile_id), mode='w'
+ ) as data_file:
submission_count = len(submissions)
if submission_count:
submission_info = {}
- with Progress(self, submission_count, stage=_('Preparing your submission data')) as p:
+ with Progress(
+ self, submission_count, stage=_('Preparing your submission data')
+ ) as p:
prepared = 0
interval = max(submission_count // 10, 1)
for submission in submissions:
@@ -77,7 +87,8 @@ def prepare_user_data(self, profile_id, options):
'case_total': submission.case_total,
}
with data_file.open(
- 'submissions/%s.%s' % (submission.id, submission.language.extension),
+ 'submissions/%s.%s'
+ % (submission.id, submission.language.extension),
'w',
) as f:
f.write(utf8bytes(submission.source.source))
@@ -87,12 +98,16 @@ def prepare_user_data(self, profile_id, options):
p.done = prepared
with data_file.open('submissions/info.json', 'w') as f:
- f.write(utf8bytes(json.dumps(submission_info, sort_keys=True, indent=4)))
+ f.write(
+ utf8bytes(json.dumps(submission_info, sort_keys=True, indent=4))
+ )
comment_count = len(comments)
if comment_count:
comment_info = {}
- with Progress(self, comment_count, stage=_('Preparing your comment data')) as p:
+ with Progress(
+ self, comment_count, stage=_('Preparing your comment data')
+ ) as p:
prepared = 0
interval = max(comment_count // 10, 1)
for comment in comments:
@@ -116,6 +131,8 @@ def prepare_user_data(self, profile_id, options):
p.done = prepared
with data_file.open('comments/info.json', 'w') as f:
- f.write(utf8bytes(json.dumps(comment_info, sort_keys=True, indent=4)))
+ f.write(
+ utf8bytes(json.dumps(comment_info, sort_keys=True, indent=4))
+ )
return submission_count + comment_count
diff --git a/judge/template_context.py b/judge/template_context.py
index 196416377d..96aae52a64 100644
--- a/judge/template_context.py
+++ b/judge/template_context.py
@@ -46,13 +46,18 @@ def comet_location(request):
else:
websocket = settings.EVENT_DAEMON_GET
poll = settings.EVENT_DAEMON_POLL
- return {'EVENT_DAEMON_LOCATION': websocket,
- 'EVENT_DAEMON_POLL_LOCATION': poll}
+ return {'EVENT_DAEMON_LOCATION': websocket, 'EVENT_DAEMON_POLL_LOCATION': poll}
def __nav_tab(path):
- result = list(NavigationBar.objects.extra(where=['%s REGEXP BINARY regex'], params=[path])[:1])
- return result[0].get_ancestors(include_self=True).values_list('key', flat=True) if result else []
+ result = list(
+ NavigationBar.objects.extra(where=['%s REGEXP BINARY regex'], params=[path])[:1]
+ )
+ return (
+ result[0].get_ancestors(include_self=True).values_list('key', flat=True)
+ if result
+ else []
+ )
def general_info(request):
@@ -75,9 +80,11 @@ def misc_config(request):
def site_name(request):
- return {'SITE_NAME': settings.SITE_NAME,
- 'SITE_LONG_NAME': settings.SITE_LONG_NAME,
- 'SITE_ADMIN_EMAIL': settings.SITE_ADMIN_EMAIL}
+ return {
+ 'SITE_NAME': settings.SITE_NAME,
+ 'SITE_LONG_NAME': settings.SITE_LONG_NAME,
+ 'SITE_ADMIN_EMAIL': settings.SITE_ADMIN_EMAIL,
+ }
def site_theme(request):
@@ -102,5 +109,7 @@ def math_setting(request):
else:
engine = settings.MATHOID_DEFAULT_TYPE
if engine == 'auto':
- engine = 'mml' if bool(settings.MATHOID_URL) and caniuse.mathml == SUPPORT else 'jax'
+ engine = (
+ 'mml' if bool(settings.MATHOID_URL) and caniuse.mathml == SUPPORT else 'jax'
+ )
return {'MATH_ENGINE': engine, 'REQUIRE_JAX': engine == 'jax', 'caniuse': caniuse}
diff --git a/judge/templatetags/strings.py b/judge/templatetags/strings.py
index 6bd685af5a..d6a86e1844 100644
--- a/judge/templatetags/strings.py
+++ b/judge/templatetags/strings.py
@@ -10,7 +10,7 @@ def split(value):
@register.filter(name='cutoff')
def cutoff(value, length):
- return value[:int(length)]
+ return value[: int(length)]
@register.filter(name='roundfloat')
diff --git a/judge/user_log.py b/judge/user_log.py
index d5304ff1b8..3c9ee1e4e9 100644
--- a/judge/user_log.py
+++ b/judge/user_log.py
@@ -10,8 +10,11 @@ def __init__(self, get_response=None):
def __call__(self, request):
response = self.get_response(request)
- if (hasattr(request, 'user') and request.user.is_authenticated and
- not getattr(request, 'no_profile_update', False)):
+ if (
+ hasattr(request, 'user')
+ and request.user.is_authenticated
+ and not getattr(request, 'no_profile_update', False)
+ ):
updates = {'last_access': now()}
# Decided on using REMOTE_ADDR as nginx will translate it to the external IP that hits it.
if request.META.get('REMOTE_ADDR'):
diff --git a/judge/user_translations.py b/judge/user_translations.py
index 29a8a6b43b..19db7c61c3 100644
--- a/judge/user_translations.py
+++ b/judge/user_translations.py
@@ -15,7 +15,9 @@ def translation(language):
def do_translate(message, translation_function):
"""Copied from django.utils.translation.trans_real"""
# str() is allowing a bytestring message to remain bytestring on Python 2
- eol_message = message.replace(str('\r\n'), str('\n')).replace(str('\r'), str('\n'))
+ eol_message = message.replace(str('\r\n'), str('\n')).replace(
+ str('\r'), str('\n')
+ )
if len(eol_message) == 0:
# Returns an empty value of the corresponding type if an empty message
@@ -34,6 +36,8 @@ def do_translate(message, translation_function):
def gettext(message):
return do_translate(message, 'gettext')
+
else:
+
def gettext(message):
return message
diff --git a/judge/utils/camo.py b/judge/utils/camo.py
index a3c90a591a..81c715c64e 100644
--- a/judge/utils/camo.py
+++ b/judge/utils/camo.py
@@ -16,9 +16,11 @@ def __init__(self, server, key, excluded=(), https=False):
self.excluded = excluded
def image_url(self, url):
- return '%s/%s/%s' % (self.server,
- hmac.new(utf8bytes(self.key), utf8bytes(url), sha1).hexdigest(),
- utf8bytes(url).hex())
+ return '%s/%s/%s' % (
+ self.server,
+ hmac.new(utf8bytes(self.key), utf8bytes(url), sha1).hexdigest(),
+ utf8bytes(url).hex(),
+ )
def rewrite_url(self, url):
if url.startswith(self.server) or url.startswith(self.excluded):
@@ -41,8 +43,11 @@ def update_tree(self, doc):
if settings.DMOJ_CAMO_URL and settings.DMOJ_CAMO_KEY:
- client = CamoClient(settings.DMOJ_CAMO_URL, key=settings.DMOJ_CAMO_KEY,
- excluded=settings.DMOJ_CAMO_EXCLUDE,
- https=settings.DMOJ_CAMO_HTTPS)
+ client = CamoClient(
+ settings.DMOJ_CAMO_URL,
+ key=settings.DMOJ_CAMO_KEY,
+ excluded=settings.DMOJ_CAMO_EXCLUDE,
+ https=settings.DMOJ_CAMO_HTTPS,
+ )
else:
client = None
diff --git a/judge/utils/caniuse.py b/judge/utils/caniuse.py
index ef77175bd6..d489736fcf 100644
--- a/judge/utils/caniuse.py
+++ b/judge/utils/caniuse.py
@@ -3,7 +3,9 @@
from ua_parser import user_agent_parser
-with open(os.path.join(os.path.dirname(__file__), '..', '..', 'resources', 'caniuse.json')) as f:
+with open(
+ os.path.join(os.path.dirname(__file__), '..', '..', 'resources', 'caniuse.json')
+) as f:
_SUPPORT_DATA = json.loads(f.read())['data']
SUPPORT = 'y'
@@ -62,7 +64,12 @@ def check(self, major, minor, patch):
if version > self.max_version:
return self.max_support
- for key in ((int_major, int_minor, int_patch), (int_major, int_minor), (int_major,), major):
+ for key in (
+ (int_major, int_minor, int_patch),
+ (int_major, int_minor),
+ (int_major,),
+ major,
+ ):
try:
return self._versions[key]
except KeyError:
@@ -78,7 +85,9 @@ def check(self, major, minor, patch):
class Feat(object):
def __init__(self, data):
self._data = data
- self._family = {name: BrowserFamily(data) for name, data in data['stats'].items()}
+ self._family = {
+ name: BrowserFamily(data) for name, data in data['stats'].items()
+ }
def __getitem__(self, item):
return self._family[item]
diff --git a/judge/utils/diggpaginator.py b/judge/utils/diggpaginator.py
index d2d8fea98e..993a35a59e 100644
--- a/judge/utils/diggpaginator.py
+++ b/judge/utils/diggpaginator.py
@@ -185,7 +185,9 @@ def __init__(self, *args, **kwargs):
self.body = kwargs.pop('body', 10)
self.tail = kwargs.pop('tail', 2)
self.align_left = kwargs.pop('align_left', False)
- self.margin = kwargs.pop('margin', 4)
+ self.margin = kwargs.pop(
+ 'margin', 4
+ ) # TODO: make the default relative to body?
# validate padding value
max_padding = int(math.ceil(self.body / 2.0) - 1)
self.padding = kwargs.pop('padding', min(4, max_padding))
@@ -205,13 +207,24 @@ def page(self, number, *args, **kwargs):
number = int(number) # we know this will work
# easier access
- num_pages, body, tail, padding, margin = \
- self.num_pages, self.body, self.tail, self.padding, self.margin
+ num_pages, body, tail, padding, margin = (
+ self.num_pages,
+ self.body,
+ self.tail,
+ self.padding,
+ self.margin,
+ )
# put active page in middle of main range
- main_range = list(map(int, [
- math.floor(number - body / 2.0) + 1, # +1 = shift odd body to right
- math.floor(number + body / 2.0)]))
+ main_range = list(
+ map(
+ int,
+ [
+ math.floor(number - body / 2.0) + 1, # +1 = shift odd body to right
+ math.floor(number + body / 2.0),
+ ],
+ )
+ )
# adjust bounds
if main_range[0] < 1:
main_range = list(map(abs(main_range[0] - 1).__add__, main_range))
@@ -252,7 +265,10 @@ def page(self, number, *args, **kwargs):
# section, again.
main_range = [1, num_pages]
else:
- main_range = [min(num_pages - body + 1, max(number - padding, main_range[0])), num_pages]
+ main_range = [
+ min(num_pages - body + 1, max(number - padding, main_range[0])),
+ num_pages,
+ ]
else:
trailing = list(range(num_pages - tail + 1, num_pages + 1))
@@ -266,8 +282,10 @@ def page(self, number, *args, **kwargs):
page.main_range = list(range(main_range[0], main_range[1] + 1))
page.leading_range = leading
page.trailing_range = trailing
- page.page_range = reduce(lambda x, y: x + ((x and y) and [False]) + y,
- [page.leading_range, page.main_range, page.trailing_range])
+ page.page_range = reduce(
+ lambda x, y: x + ((x and y) and [False]) + y,
+ [page.leading_range, page.main_range, page.trailing_range],
+ )
page.__class__ = DiggPage
return page
@@ -275,10 +293,16 @@ def page(self, number, *args, **kwargs):
class DiggPage(Page):
def __str__(self):
- return ' ... '.join(filter(None, [
- ' '.join(map(str, self.leading_range)),
- ' '.join(map(str, self.main_range)),
- ' '.join(map(str, self.trailing_range))]))
+ return ' ... '.join(
+ filter(
+ None,
+ [
+ ' '.join(map(str, self.leading_range)),
+ ' '.join(map(str, self.main_range)),
+ ' '.join(map(str, self.trailing_range)),
+ ],
+ )
+ )
@property
def num_pages(self):
diff --git a/judge/utils/infinite_paginator.py b/judge/utils/infinite_paginator.py
index 06e4f2198f..7b8a1a36b5 100644
--- a/judge/utils/infinite_paginator.py
+++ b/judge/utils/infinite_paginator.py
@@ -9,7 +9,9 @@
class InfinitePage(collections.abc.Sequence):
- def __init__(self, object_list, number, unfiltered_queryset, page_size, pad_pages, paginator):
+ 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
@@ -31,7 +33,9 @@ def __getitem__(self, index):
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]
+ 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()
@@ -65,7 +69,9 @@ def end_index(self):
@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)
+ end = self.number + min(
+ int(ceil(self._after_up_to_pad / self.page_size)), self.pad_pages
+ )
return range(start, end + 1)
@cached_property
@@ -103,7 +109,7 @@ def __init__(self, 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]
+ 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)
@@ -118,7 +124,9 @@ def use_infinite_pagination(self):
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, page, object_list, has_other = super().paginate_queryset(
+ queryset, page_size
+ )
paginator.is_infinite = False
return paginator, page, object_list, has_other
@@ -130,10 +138,15 @@ def paginate_queryset(self, queryset, page_size):
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)
+ 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),
- })
+ raise Http404(
+ 'Invalid page (%(page_number)s): %(message)s'
+ % {
+ 'page_number': page_number,
+ 'message': str(e),
+ }
+ )
diff --git a/judge/utils/mail.py b/judge/utils/mail.py
index a7ab3f37aa..f8485ad0bc 100644
--- a/judge/utils/mail.py
+++ b/judge/utils/mail.py
@@ -8,15 +8,23 @@
from django.utils.translation import gettext
-bad_mail_regex: List[Pattern[str]] = list(map(re.compile, settings.BAD_MAIL_PROVIDER_REGEX))
+bad_mail_regex: List[Pattern[str]] = list(
+ map(re.compile, settings.BAD_MAIL_PROVIDER_REGEX)
+)
def validate_email_domain(email: str) -> None:
if '@' in email:
domain = email.split('@')[-1].lower()
- if domain in settings.BAD_MAIL_PROVIDERS or any(regex.match(domain) for regex in bad_mail_regex):
- raise ValidationError(gettext('Your email provider is not allowed due to history of abuse. '
- 'Please use a reputable email provider.'))
+ if domain in settings.BAD_MAIL_PROVIDERS or any(
+ regex.match(domain) for regex in bad_mail_regex
+ ):
+ raise ValidationError(
+ gettext(
+ 'Your email provider is not allowed due to history of abuse. '
+ 'Please use a reputable email provider.'
+ )
+ )
# Inspired by django.contrib.auth.forms.PasswordResetForm.send_mail
diff --git a/judge/utils/mathoid.py b/judge/utils/mathoid.py
index ae45500568..5fbbfc67d7 100644
--- a/judge/utils/mathoid.py
+++ b/judge/utils/mathoid.py
@@ -46,9 +46,11 @@ def __init__(self, type):
self.type = type
self.mathoid_url = settings.MATHOID_URL
- self.cache = HashFileCache(settings.MATHOID_CACHE_ROOT,
- settings.MATHOID_CACHE_URL,
- settings.MATHOID_GZIP)
+ self.cache = HashFileCache(
+ settings.MATHOID_CACHE_ROOT,
+ settings.MATHOID_CACHE_URL,
+ settings.MATHOID_GZIP,
+ )
mml_cache = settings.MATHOID_MML_CACHE
self.mml_cache = mml_cache and caches[mml_cache]
@@ -60,10 +62,17 @@ def query_mathoid(self, formula, hash):
self.cache.create(hash)
try:
- response = requests.post(self.mathoid_url, data={
- 'q': reescape.sub(lambda m: '\\' + m.group(0), formula).encode('utf-8'),
- 'type': 'tex' if formula.startswith(r'\displaystyle') else 'inline-tex',
- })
+ response = requests.post(
+ self.mathoid_url,
+ data={
+ 'q': reescape.sub(lambda m: '\\' + m.group(0), formula).encode(
+ 'utf-8'
+ ),
+ 'type': 'tex'
+ if formula.startswith(r'\displaystyle')
+ else 'inline-tex',
+ },
+ )
response.raise_for_status()
data = response.json()
except requests.ConnectionError:
@@ -81,7 +90,10 @@ def query_mathoid(self, formula, hash):
return
if any(i not in data for i in ('mml', 'svg', 'mathoidStyle')):
- logger.error('Mathoid did not return required information (mml, svg, mathoidStyle needed):\n%s', data)
+ logger.error(
+ 'Mathoid did not return required information (mml, svg, mathoidStyle needed):\n%s',
+ data,
+ )
return
css = data['mathoidStyle']
@@ -140,17 +152,26 @@ def output_mml(self, result):
return result['mml']
def output_jax(self, result):
- return format_html(''
- ' '
- '{4}{2}{4} '
- ' ',
- result['svg'], result['css'], result['tex'],
- ['inline-math', 'display-math'][result['display']], ['~', '$$'][result['display']])
+ return format_html(
+ ''
+ ' '
+ '{4}{2}{4} '
+ ' ',
+ result['svg'],
+ result['css'],
+ result['tex'],
+ ['inline-math', 'display-math'][result['display']],
+ ['~', '$$'][result['display']],
+ )
def output_svg(self, result):
- return format_html(' ',
- result['svg'], result['css'], result['tex'],
- ['inline-math', 'display-math'][result['display']])
+ return format_html(
+ ' ',
+ result['svg'],
+ result['css'],
+ result['tex'],
+ ['inline-math', 'display-math'][result['display']],
+ )
def display_math(self, math):
math = format_math(math)
diff --git a/judge/utils/pdfoid.py b/judge/utils/pdfoid.py
index 9fd6fc3c0d..cd3e23ed21 100644
--- a/judge/utils/pdfoid.py
+++ b/judge/utils/pdfoid.py
@@ -18,9 +18,10 @@ def render_pdf(*, title: str, html: str, footer: bool = False) -> bytes:
if footer:
footer_template = (
- '' +
- gettext('Page {page_number} of {total_pages}') +
- ' ')
+ ''
+ + gettext('Page {page_number} of {total_pages}')
+ + ' '
+ )
else:
footer_template = None
diff --git a/judge/utils/problem_data.py b/judge/utils/problem_data.py
index ce59d29adb..0107fb4923 100644
--- a/judge/utils/problem_data.py
+++ b/judge/utils/problem_data.py
@@ -10,9 +10,14 @@
from django.utils.translation import gettext as _
if os.altsep:
- def split_path_first(path, repath=re.compile('[%s]' % re.escape(os.sep + os.altsep))):
+
+ def split_path_first(
+ path, repath=re.compile('[%s]' % re.escape(os.sep + os.altsep))
+ ):
return repath.split(path, 1)
+
else:
+
def split_path_first(path):
return path.split(os.sep, 1)
@@ -79,16 +84,22 @@ def make_checker(case):
case.is_pretest = batch['is_pretest']
else:
if case.points is None:
- raise ProblemDataError(_('Points must be defined for non-batch case #%d.') % i)
+ raise ProblemDataError(
+ _('Points must be defined for non-batch case #%d.') % i
+ )
data['is_pretest'] = case.is_pretest
if not self.generator:
if case.input_file not in self.files:
- raise ProblemDataError(_('Input file for case %d does not exist: %s') %
- (i, case.input_file))
+ raise ProblemDataError(
+ _('Input file for case %d does not exist: %s')
+ % (i, case.input_file)
+ )
if case.output_file not in self.files:
- raise ProblemDataError(_('Output file for case %d does not exist: %s') %
- (i, case.output_file))
+ raise ProblemDataError(
+ _('Output file for case %d does not exist: %s')
+ % (i, case.output_file)
+ )
if case.input_file:
data['in'] = case.input_file
@@ -112,7 +123,9 @@ def make_checker(case):
if batch:
end_batch()
if case.points is None:
- raise ProblemDataError(_('Batch start case #%d requires points.') % i)
+ raise ProblemDataError(
+ _('Batch start case #%d requires points.') % i
+ )
batch = {
'points': case.points,
'batched': [],
@@ -133,7 +146,9 @@ def make_checker(case):
case.save(update_fields=('checker_args', 'input_file', 'output_file'))
elif case.type == 'E':
if not batch:
- raise ProblemDataError(_('Attempt to end batch outside of one in case #%d.') % i)
+ raise ProblemDataError(
+ _('Attempt to end batch outside of one in case #%d.') % i
+ )
case.is_pretest = batch['is_pretest']
case.input_file = ''
case.output_file = ''
diff --git a/judge/utils/problems.py b/judge/utils/problems.py
index 44e7af4576..ead2a083f8 100644
--- a/judge/utils/problems.py
+++ b/judge/utils/problems.py
@@ -9,11 +9,21 @@
from judge.models import Problem, Submission
-__all__ = ['contest_completed_ids', 'get_result_data', 'user_completed_ids', 'user_editable_ids', 'user_tester_ids']
+__all__ = [
+ 'contest_completed_ids',
+ 'get_result_data',
+ 'user_completed_ids',
+ 'user_editable_ids',
+ 'user_tester_ids',
+]
def user_tester_ids(profile):
- return set(Problem.testers.through.objects.filter(profile=profile).values_list('problem_id', flat=True))
+ return set(
+ Problem.testers.through.objects.filter(profile=profile).values_list(
+ 'problem_id', flat=True
+ )
+ )
def user_editable_ids(profile):
@@ -24,8 +34,13 @@ def contest_completed_ids(participation):
key = 'contest_complete:%d' % participation.id
result = cache.get(key)
if result is None:
- result = set(participation.submissions.filter(submission__result='AC', points__gte=F('problem__points'))
- .values_list('problem__problem_id', flat=True).distinct())
+ result = set(
+ participation.submissions.filter(
+ submission__result='AC', points__gte=F('problem__points')
+ )
+ .values_list('problem__problem_id', flat=True)
+ .distinct()
+ )
cache.set(key, result, 86400)
return result
@@ -34,8 +49,13 @@ def user_completed_ids(profile):
key = 'user_complete:%d' % profile.id
result = cache.get(key)
if result is None:
- result = set(Submission.objects.filter(user=profile, result='AC', case_points__gte=F('case_total'))
- .values_list('problem_id', flat=True).distinct())
+ result = set(
+ Submission.objects.filter(
+ user=profile, result='AC', case_points__gte=F('case_total')
+ )
+ .values_list('problem_id', flat=True)
+ .distinct()
+ )
cache.set(key, result, 86400)
return result
@@ -44,7 +64,11 @@ def contest_attempted_ids(participation):
key = 'contest_attempted:%s' % participation.id
result = cache.get(key)
if result is None:
- result = set(participation.submissions.values_list('problem__problem_id', flat=True).distinct())
+ result = set(
+ participation.submissions.values_list(
+ 'problem__problem_id', flat=True
+ ).distinct()
+ )
cache.set(key, result, 86400)
return result
@@ -53,7 +77,9 @@ def user_attempted_ids(profile):
key = 'user_attempted:%s' % profile.id
result = cache.get(key)
if result is None:
- result = set(profile.submission_set.values_list('problem_id', flat=True).distinct())
+ result = set(
+ profile.submission_set.values_list('problem_id', flat=True).distinct()
+ )
cache.set(key, result, 86400)
return result
@@ -65,10 +91,22 @@ def _get_result_data(results):
# The caller, SubmissionList.get_result_data will run gettext on the name.
{'code': 'AC', 'name': gettext_noop('Accepted'), 'count': results['AC']},
{'code': 'WA', 'name': gettext_noop('Wrong'), 'count': results['WA']},
- {'code': 'CE', 'name': gettext_noop('Compile Error'), 'count': results['CE']},
+ {
+ 'code': 'CE',
+ 'name': gettext_noop('Compile Error'),
+ 'count': results['CE'],
+ },
{'code': 'TLE', 'name': gettext_noop('Timeout'), 'count': results['TLE']},
- {'code': 'ERR', 'name': gettext_noop('Error'),
- 'count': results['MLE'] + results['OLE'] + results['IR'] + results['RTE'] + results['AB'] + results['IE']},
+ {
+ 'code': 'ERR',
+ 'name': gettext_noop('Error'),
+ 'count': results['MLE']
+ + results['OLE']
+ + results['IR']
+ + results['RTE']
+ + results['AB']
+ + results['IE'],
+ },
],
'total': sum(results.values()),
}
@@ -80,8 +118,16 @@ def get_result_data(*args, **kwargs):
if kwargs:
raise ValueError("Can't pass both queryset and keyword filters")
else:
- submissions = Submission.objects.filter(**kwargs) if kwargs is not None else Submission.objects
- raw = submissions.values('result').annotate(count=Count('result')).values_list('result', 'count')
+ submissions = (
+ Submission.objects.filter(**kwargs)
+ if kwargs is not None
+ else Submission.objects
+ )
+ raw = (
+ submissions.values('result')
+ .annotate(count=Count('result'))
+ .values_list('result', 'count')
+ )
return _get_result_data(defaultdict(int, raw))
@@ -89,9 +135,14 @@ def hot_problems(duration, limit):
cache_key = 'hot_problems:%d:%d' % (duration.total_seconds(), limit)
qs = cache.get(cache_key)
if qs is None:
- qs = Problem.get_public_problems() \
- .filter(submission__date__gt=timezone.now() - duration, points__gt=3, points__lt=25)
- qs0 = qs.annotate(k=Count('submission__user', distinct=True)).order_by('-k').values_list('k', flat=True)
+ qs = Problem.get_public_problems().filter(
+ submission__date__gt=timezone.now() - duration, points__gt=3, points__lt=25
+ )
+ qs0 = (
+ qs.annotate(k=Count('submission__user', distinct=True))
+ .order_by('-k')
+ .values_list('k', flat=True)
+ )
if not qs0:
return []
@@ -100,25 +151,45 @@ def hot_problems(duration, limit):
qs = qs.annotate(unique_user_count=Count('submission__user', distinct=True))
# fix braindamage in excluding CE
- qs = qs.annotate(submission_volume=Count(Case(
- When(submission__result='AC', then=1),
- When(submission__result='WA', then=1),
- When(submission__result='IR', then=1),
- When(submission__result='RTE', then=1),
- When(submission__result='TLE', then=1),
- When(submission__result='OLE', then=1),
- output_field=FloatField(),
- )))
- qs = qs.annotate(ac_volume=Count(Case(
- When(submission__result='AC', then=1),
- output_field=FloatField(),
- )))
+ qs = qs.annotate(
+ submission_volume=Count(
+ Case(
+ When(submission__result='AC', then=1),
+ When(submission__result='WA', then=1),
+ When(submission__result='IR', then=1),
+ When(submission__result='RTE', then=1),
+ When(submission__result='TLE', then=1),
+ When(submission__result='OLE', then=1),
+ output_field=FloatField(),
+ )
+ )
+ )
+ qs = qs.annotate(
+ ac_volume=Count(
+ Case(
+ When(submission__result='AC', then=1),
+ output_field=FloatField(),
+ )
+ )
+ )
qs = qs.filter(unique_user_count__gt=max(mx / 3.0, 1))
- qs = qs.annotate(ordering=ExpressionWrapper(
- 0.5 * F('points') * (0.4 * F('ac_volume') / F('submission_volume') + 0.6 * F('ac_rate')) +
- 100 * e ** (F('unique_user_count') / mx), output_field=FloatField(),
- )).order_by('-ordering').defer('description')[:limit]
+ qs = (
+ qs.annotate(
+ ordering=ExpressionWrapper(
+ 0.5
+ * F('points')
+ * (
+ 0.4 * F('ac_volume') / F('submission_volume')
+ + 0.6 * F('ac_rate')
+ )
+ + 100 * e ** (F('unique_user_count') / mx),
+ output_field=FloatField(),
+ )
+ )
+ .order_by('-ordering')
+ .defer('description')[:limit]
+ )
cache.set(cache_key, qs, 900)
return qs
diff --git a/judge/utils/raw_sql.py b/judge/utils/raw_sql.py
index bbf7235bd8..9dbe552ca2 100644
--- a/judge/utils/raw_sql.py
+++ b/judge/utils/raw_sql.py
@@ -6,10 +6,27 @@
class RawSQLJoin(Join):
- def __init__(self, subquery, subquery_params, parent_alias, table_alias, join_type, join_field, nullable,
- filtered_relation=None):
+ def __init__(
+ self,
+ subquery,
+ subquery_params,
+ parent_alias,
+ table_alias,
+ join_type,
+ join_field,
+ nullable,
+ filtered_relation=None,
+ ):
self.subquery_params = subquery_params
- super().__init__(subquery, parent_alias, table_alias, join_type, join_field, nullable, filtered_relation)
+ super().__init__(
+ subquery,
+ parent_alias,
+ table_alias,
+ join_type,
+ join_field,
+ nullable,
+ filtered_relation,
+ )
def as_sql(self, compiler, connection):
compiler.quote_cache[self.table_name] = '(%s)' % self.table_name
@@ -30,7 +47,15 @@ def get_extra_restriction(self, where_class, alias, remote_alias):
def join_sql_subquery(
- queryset, subquery, params, join_fields, alias, related_model, join_type=INNER, parent_model=None):
+ queryset,
+ subquery,
+ params,
+ join_fields,
+ alias,
+ related_model,
+ join_type=INNER,
+ parent_model=None,
+):
if parent_model is not None:
parent_alias = parent_model._meta.db_table
else:
@@ -39,8 +64,15 @@ def join_sql_subquery(
queryset.query.external_aliases[alias] = True
else:
queryset.query.external_aliases.add(alias)
- join = RawSQLJoin(subquery, params, parent_alias, alias, join_type, FakeJoinField(join_fields, related_model),
- join_type == LOUTER)
+ join = RawSQLJoin(
+ subquery,
+ params,
+ parent_alias,
+ alias,
+ join_type,
+ FakeJoinField(join_fields, related_model),
+ join_type == LOUTER,
+ )
queryset.query.join(join)
join.table_alias = alias
diff --git a/judge/utils/recaptcha.py b/judge/utils/recaptcha.py
index 331a12d0b7..d11f8efb1e 100644
--- a/judge/utils/recaptcha.py
+++ b/judge/utils/recaptcha.py
@@ -6,6 +6,7 @@
ReCaptchaWidget = None
else:
from django.conf import settings
+
if not hasattr(settings, 'RECAPTCHA_PRIVATE_KEY'):
ReCaptchaField = None
ReCaptchaWidget = None
diff --git a/judge/utils/safe_translations.py b/judge/utils/safe_translations.py
index 9d95149d78..12093abe35 100644
--- a/judge/utils/safe_translations.py
+++ b/judge/utils/safe_translations.py
@@ -12,7 +12,15 @@ def wrapper(*args, **kwargs):
return wrapper
- for func in ['gettext', 'gettext_lazy', 'gettext_noop', 'ngettext', 'ngettext_lazy', 'pgettext', 'pgettext_lazy']:
+ for func in [
+ 'gettext',
+ 'gettext_lazy',
+ 'gettext_noop',
+ 'ngettext',
+ 'ngettext_lazy',
+ 'pgettext',
+ 'pgettext_lazy',
+ ]:
globals()[func] = wrap(getattr(translation, func))
diff --git a/judge/utils/stats.py b/judge/utils/stats.py
index 450a61afce..c9d2a470f0 100644
--- a/judge/utils/stats.py
+++ b/judge/utils/stats.py
@@ -3,13 +3,37 @@
__all__ = ('chart_colors', 'highlight_colors', 'get_pie_chart', 'get_bar_chart')
-BASE_COLORS = [0x3366CC, 0xDC3912, 0xFF9900, 0x109618, 0x990099, 0x3B3EAC, 0x0099C6, 0xDD4477, 0x66AA00, 0xB82E2E,
- 0x316395, 0x994499, 0x22AA99, 0xAAAA11, 0x6633CC, 0xE67300, 0x8B0707, 0x329262, 0x5574A6, 0x3B3EAC]
+BASE_COLORS = [
+ 0x3366CC,
+ 0xDC3912,
+ 0xFF9900,
+ 0x109618,
+ 0x990099,
+ 0x3B3EAC,
+ 0x0099C6,
+ 0xDD4477,
+ 0x66AA00,
+ 0xB82E2E,
+ 0x316395,
+ 0x994499,
+ 0x22AA99,
+ 0xAAAA11,
+ 0x6633CC,
+ 0xE67300,
+ 0x8B0707,
+ 0x329262,
+ 0x5574A6,
+ 0x3B3EAC,
+]
def _to_highlight_color(color):
r, g, b = color >> 16, (color >> 8) & 0xFF, color & 0xFF
- return '#%02X%02X%02X' % (min(int(r * 1.2), 255), min(int(g * 1.2), 255), min(int(b * 1.2), 255))
+ return '#%02X%02X%02X' % (
+ min(int(r * 1.2), 255),
+ min(int(g * 1.2), 255),
+ min(int(b * 1.2), 255),
+ )
chart_colors = list(map('#%06X'.__mod__, BASE_COLORS))
@@ -37,8 +61,12 @@ def get_bar_chart(data, **kwargs):
'backgroundColor': kwargs.get('fillColor', 'rgba(151,187,205,0.5)'),
'borderColor': kwargs.get('strokeColor', 'rgba(151,187,205,0.8)'),
'borderWidth': 1,
- 'hoverBackgroundColor': kwargs.get('highlightFill', 'rgba(151,187,205,0.75)'),
- 'hoverBorderColor': kwargs.get('highlightStroke', 'rgba(151,187,205,1)'),
+ 'hoverBackgroundColor': kwargs.get(
+ 'highlightFill', 'rgba(151,187,205,0.75)'
+ ),
+ 'hoverBorderColor': kwargs.get(
+ 'highlightStroke', 'rgba(151,187,205,1)'
+ ),
'data': list(map(itemgetter(1), data)),
},
],
diff --git a/judge/utils/subscription.py b/judge/utils/subscription.py
index 1129b0d47c..5049da1990 100644
--- a/judge/utils/subscription.py
+++ b/judge/utils/subscription.py
@@ -5,4 +5,6 @@
else:
Subscription = None
-newsletter_id = None if Subscription is None else settings.DMOJ_NEWSLETTER_ID_ON_REGISTER
+newsletter_id = (
+ None if Subscription is None else settings.DMOJ_NEWSLETTER_ID_ON_REGISTER
+)
diff --git a/judge/utils/tests/test_infinite_paginator.py b/judge/utils/tests/test_infinite_paginator.py
index a177ba5fa6..08ac5bb4f0 100644
--- a/judge/utils/tests/test_infinite_paginator.py
+++ b/judge/utils/tests/test_infinite_paginator.py
@@ -5,11 +5,19 @@
class InfinitePaginatorTestCase(SimpleTestCase):
def test_first_page(self):
- self.assertEqual(infinite_paginate(range(1, 101), 1, 10, 2).object_list, list(range(1, 11)))
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 1, 10, 2).object_list, list(range(1, 11))
+ )
- self.assertEqual(infinite_paginate(range(1, 101), 1, 10, 2).page_range, [1, 2, 3, False])
- self.assertEqual(infinite_paginate(range(1, 31), 1, 10, 2).page_range, [1, 2, 3])
- self.assertEqual(infinite_paginate(range(1, 22), 1, 10, 2).page_range, [1, 2, 3])
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 1, 10, 2).page_range, [1, 2, 3, False]
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 31), 1, 10, 2).page_range, [1, 2, 3]
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 22), 1, 10, 2).page_range, [1, 2, 3]
+ )
self.assertEqual(infinite_paginate(range(1, 21), 1, 10, 2).page_range, [1, 2])
self.assertEqual(infinite_paginate(range(1, 12), 1, 10, 2).page_range, [1, 2])
self.assertEqual(infinite_paginate(range(1, 11), 1, 10, 2).page_range, [1])
@@ -17,16 +25,47 @@ def test_first_page(self):
self.assertEqual(infinite_paginate([], 1, 10, 2).page_range, [1])
def test_gaps(self):
- self.assertEqual(infinite_paginate(range(1, 101), 1, 10, 2).page_range, [1, 2, 3, False])
- self.assertEqual(infinite_paginate(range(1, 101), 2, 10, 2).page_range, [1, 2, 3, 4, False])
- self.assertEqual(infinite_paginate(range(1, 101), 3, 10, 2).page_range, [1, 2, 3, 4, 5, False])
- self.assertEqual(infinite_paginate(range(1, 101), 5, 10, 2).page_range, [1, 2, 3, 4, 5, 6, 7, False])
- self.assertEqual(infinite_paginate(range(1, 101), 6, 10, 2).page_range, [1, 2, False, 4, 5, 6, 7, 8, False])
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 1, 10, 2).page_range, [1, 2, 3, False]
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 2, 10, 2).page_range, [1, 2, 3, 4, False]
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 3, 10, 2).page_range,
+ [1, 2, 3, 4, 5, False],
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 5, 10, 2).page_range,
+ [1, 2, 3, 4, 5, 6, 7, False],
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 6, 10, 2).page_range,
+ [1, 2, False, 4, 5, 6, 7, 8, False],
+ )
def test_end(self):
- self.assertEqual(infinite_paginate(range(1, 101), 7, 10, 2).page_range, [1, 2, False, 5, 6, 7, 8, 9, False])
- self.assertEqual(infinite_paginate(range(1, 101), 8, 10, 2).page_range, [1, 2, False, 6, 7, 8, 9, 10])
- self.assertEqual(infinite_paginate(range(1, 101), 9, 10, 2).page_range, [1, 2, False, 7, 8, 9, 10])
- self.assertEqual(infinite_paginate(range(1, 101), 10, 10, 2).page_range, [1, 2, False, 8, 9, 10])
- self.assertEqual(infinite_paginate(range(1, 100), 10, 10, 2).page_range, [1, 2, False, 8, 9, 10])
- self.assertEqual(infinite_paginate(range(1, 100), 10, 10, 2).object_list, list(range(91, 100)))
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 7, 10, 2).page_range,
+ [1, 2, False, 5, 6, 7, 8, 9, False],
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 8, 10, 2).page_range,
+ [1, 2, False, 6, 7, 8, 9, 10],
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 9, 10, 2).page_range,
+ [1, 2, False, 7, 8, 9, 10],
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 101), 10, 10, 2).page_range,
+ [1, 2, False, 8, 9, 10],
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 100), 10, 10, 2).page_range,
+ [1, 2, False, 8, 9, 10],
+ )
+ self.assertEqual(
+ infinite_paginate(range(1, 100), 10, 10, 2).object_list,
+ list(range(91, 100)),
+ )
diff --git a/judge/utils/texoid.py b/judge/utils/texoid.py
index 1f57dd0198..bfb8b74076 100644
--- a/judge/utils/texoid.py
+++ b/judge/utils/texoid.py
@@ -17,9 +17,9 @@
class TexoidRenderer(object):
def __init__(self):
- self.cache = HashFileCache(settings.TEXOID_CACHE_ROOT,
- settings.TEXOID_CACHE_URL,
- settings.TEXOID_GZIP)
+ self.cache = HashFileCache(
+ settings.TEXOID_CACHE_ROOT, settings.TEXOID_CACHE_URL, settings.TEXOID_GZIP
+ )
self.meta_cache = caches[settings.TEXOID_META_CACHE]
self.meta_cache_ttl = settings.TEXOID_META_CACHE_TTL
@@ -27,13 +27,19 @@ def query_texoid(self, document, hash):
self.cache.create(hash)
try:
- response = requests.post(settings.TEXOID_URL, data=utf8bytes(document), headers={
- 'Content-Type': 'application/x-tex',
- })
+ response = requests.post(
+ settings.TEXOID_URL,
+ data=utf8bytes(document),
+ headers={
+ 'Content-Type': 'application/x-tex',
+ },
+ )
response.raise_for_status()
except requests.HTTPError as e:
if e.response.status_code == 400:
- logger.error('Texoid failed to render: %s\n%s', document, e.response.text)
+ logger.error(
+ 'Texoid failed to render: %s\n%s', document, e.response.text
+ )
else:
logger.exception('Failed to connect to texoid for: %s', document)
return
@@ -44,7 +50,9 @@ def query_texoid(self, document, hash):
try:
data = response.json()
except ValueError:
- logger.exception('Invalid texoid response for: %s\n%s', document, response.text)
+ logger.exception(
+ 'Invalid texoid response for: %s\n%s', document, response.text
+ )
return
if not data['success']:
@@ -52,7 +60,9 @@ def query_texoid(self, document, hash):
return {'error': data['error']}
meta = data['meta']
- self.cache.cache_data(hash, 'meta', utf8bytes(json.dumps(meta)), url=False, gzip=False)
+ self.cache.cache_data(
+ hash, 'meta', utf8bytes(json.dumps(meta)), url=False, gzip=False
+ )
result = {
'png': self.cache.cache_data(hash, 'png', b64decode(data['png'])),
diff --git a/judge/utils/tickets.py b/judge/utils/tickets.py
index 28973d6ee7..50a4b83e3e 100644
--- a/judge/utils/tickets.py
+++ b/judge/utils/tickets.py
@@ -9,6 +9,10 @@ def own_ticket_filter(profile_id):
def filter_visible_tickets(queryset, user):
- return queryset.filter(own_ticket_filter(user.profile.id) |
- Q(content_type=ContentType.objects.get_for_model(Problem),
- object_id__in=Problem.get_editable_problems(user))).distinct()
+ return queryset.filter(
+ own_ticket_filter(user.profile.id)
+ | Q(
+ content_type=ContentType.objects.get_for_model(Problem),
+ object_id__in=Problem.get_editable_problems(user),
+ )
+ ).distinct()
diff --git a/judge/utils/timedelta.py b/judge/utils/timedelta.py
index cb1b4d7521..aab5261d8b 100644
--- a/judge/utils/timedelta.py
+++ b/judge/utils/timedelta.py
@@ -16,7 +16,9 @@ def nice_repr(timedelta, display='long', sep=', '):
'1d, 1s'
"""
- assert isinstance(timedelta, datetime.timedelta), 'First argument must be a timedelta.'
+ assert isinstance(
+ timedelta, datetime.timedelta
+ ), 'First argument must be a timedelta.'
result = []
@@ -30,8 +32,8 @@ def nice_repr(timedelta, display='long', sep=', '):
days += weeks * 7
if days:
if hours or minutes:
- return '%d day%s %d:%02d' % (days, 's'[days == 1:], hours, minutes)
- return '%d day%s' % (days, 's'[days == 1:])
+ return '%d day%s %d:%02d' % (days, 's'[days == 1 :], hours, minutes)
+ return '%d day%s' % (days, 's'[days == 1 :])
else:
return '%d:%02d' % (hours, minutes)
elif display == 'sql':
@@ -40,27 +42,56 @@ def nice_repr(timedelta, display='long', sep=', '):
elif display == 'simple':
days += weeks * 7
if days:
- return '%d day%s %02d:%02d:%02d' % (days, 's'[days == 1:], hours, minutes, seconds)
+ return '%d day%s %02d:%02d:%02d' % (
+ days,
+ 's'[days == 1 :],
+ hours,
+ minutes,
+ seconds,
+ )
else:
return '%02d:%02d:%02d' % (hours, minutes, seconds)
elif display == 'localized':
days += weeks * 7
if days:
- return npgettext('time format with day', '%d day %h:%m:%s', '%d days %h:%m:%s', days) \
- .replace('%d', str(days)).replace('%h', '%02d' % hours).replace('%m', '%02d' % minutes) \
+ return (
+ npgettext(
+ 'time format with day', '%d day %h:%m:%s', '%d days %h:%m:%s', days
+ )
+ .replace('%d', str(days))
+ .replace('%h', '%02d' % hours)
+ .replace('%m', '%02d' % minutes)
.replace('%s', '%02d' % seconds)
+ )
else:
- return pgettext('time format without day', '%h:%m:%s') \
- .replace('%h', '%02d' % hours).replace('%m', '%02d' % minutes).replace('%s', '%02d' % seconds)
+ return (
+ pgettext('time format without day', '%h:%m:%s')
+ .replace('%h', '%02d' % hours)
+ .replace('%m', '%02d' % minutes)
+ .replace('%s', '%02d' % seconds)
+ )
elif display == 'localized-no-seconds':
days += weeks * 7
if days:
if hours or minutes:
- return npgettext('time format no seconds with day', '%d day %h:%m', '%d days %h:%m', days) \
- .replace('%d', str(days)).replace('%h', '%02d' % hours).replace('%m', '%02d' % minutes)
+ return (
+ npgettext(
+ 'time format no seconds with day',
+ '%d day %h:%m',
+ '%d days %h:%m',
+ days,
+ )
+ .replace('%d', str(days))
+ .replace('%h', '%02d' % hours)
+ .replace('%m', '%02d' % minutes)
+ )
return ngettext('%d day', '%d days', days) % days
else:
- return pgettext('hours and minutes', '%h:%m').replace('%h', '%02d' % hours).replace('%m', '%02d' % minutes)
+ return (
+ pgettext('hours and minutes', '%h:%m')
+ .replace('%h', '%02d' % hours)
+ .replace('%m', '%02d' % minutes)
+ )
elif display == 'concise':
days += weeks * 7
if days:
diff --git a/judge/utils/views.py b/judge/utils/views.py
index 6abe796c0e..5ef1e59d04 100644
--- a/judge/utils/views.py
+++ b/judge/utils/views.py
@@ -6,14 +6,21 @@
def generic_message(request, title, message, status=None):
- return render(request, 'generic-message.html', {
- 'message': message,
- 'title': title,
- }, status=status)
+ return render(
+ request,
+ 'generic-message.html',
+ {
+ 'message': message,
+ 'title': title,
+ },
+ status=status,
+ )
def add_file_response(request, response, url_path, file_path, file_object=None):
- if url_path is not None and request.META.get('SERVER_SOFTWARE', '').startswith('nginx/'):
+ if url_path is not None and request.META.get('SERVER_SOFTWARE', '').startswith(
+ 'nginx/'
+ ):
response['X-Accel-Redirect'] = url_path
else:
if file_object is None:
@@ -29,11 +36,15 @@ def paginate_query_context(request):
query.setlist('page', [])
query = query.urlencode()
if query:
- return {'page_prefix': '%s?%s&page=' % (request.path, query),
- 'first_page_href': '%s?%s' % (request.path, query)}
+ return {
+ 'page_prefix': '%s?%s&page=' % (request.path, query),
+ 'first_page_href': '%s?%s' % (request.path, query),
+ }
else:
- return {'page_prefix': '%s?page=' % request.path,
- 'first_page_href': request.path}
+ return {
+ 'page_prefix': '%s?page=' % request.path,
+ 'first_page_href': request.path,
+ }
class NoBatchDeleteMixin(object):
@@ -64,10 +75,18 @@ def get_title(self):
class DiggPaginatorMixin(object):
- 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_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
+ )
class QueryStringSortMixin(object):
@@ -80,7 +99,10 @@ def get_default_sort_order(self, request):
def get(self, request, *args, **kwargs):
order = request.GET.get('order', '')
- if not ((not order.startswith('-') or order.count('-') == 1) and (order.lstrip('-') in self.all_sorts)):
+ if not (
+ (not order.startswith('-') or order.count('-') == 1)
+ and (order.lstrip('-') in self.all_sorts)
+ ):
order = self.get_default_sort_order(request)
self.order = order
@@ -90,11 +112,20 @@ def get_sort_context(self):
query = self.request.GET.copy()
query.setlist('order', [])
query = query.urlencode()
- sort_prefix = '%s?%s&order=' % (self.request.path, query) if query else '%s?order=' % self.request.path
+ sort_prefix = (
+ '%s?%s&order=' % (self.request.path, query)
+ if query
+ else '%s?order=' % self.request.path
+ )
current = self.order.lstrip('-')
- links = {key: sort_prefix + ('-' if key in self.default_desc else '') + key for key in self.all_sorts}
- links[current] = sort_prefix + ('' if self.order.startswith('-') else '-') + current
+ links = {
+ key: sort_prefix + ('-' if key in self.default_desc else '') + key
+ for key in self.all_sorts
+ }
+ links[current] = (
+ sort_prefix + ('' if self.order.startswith('-') else '-') + current
+ )
order = {key: '' for key in self.all_sorts}
order[current] = ' \u25BE' if self.order.startswith('-') else ' \u25B4'
diff --git a/judge/views/api/__init__.py b/judge/views/api/__init__.py
index f25bab089b..14f20e4088 100644
--- a/judge/views/api/__init__.py
+++ b/judge/views/api/__init__.py
@@ -1,4 +1,12 @@
from .api_v2 import (
- APIContestDetail, APIContestList, APIContestParticipationList, APIOrganizationList, APIProblemDetail,
- APIProblemList, APISubmissionDetail, APISubmissionList, APIUserDetail, APIUserList,
+ APIContestDetail,
+ APIContestList,
+ APIContestParticipationList,
+ APIOrganizationList,
+ APIProblemDetail,
+ APIProblemList,
+ APISubmissionDetail,
+ APISubmissionList,
+ APIUserDetail,
+ APIUserList,
)
diff --git a/judge/views/api/api_v2.py b/judge/views/api/api_v2.py
index dd45c284b9..38677e2534 100644
--- a/judge/views/api/api_v2.py
+++ b/judge/views/api/api_v2.py
@@ -10,7 +10,16 @@
from django.views.generic.list import BaseListView
from judge.models import (
- Contest, ContestParticipation, ContestTag, Judge, Language, Organization, Problem, ProblemType, Profile, Rating,
+ Contest,
+ ContestParticipation,
+ ContestTag,
+ Judge,
+ Language,
+ Organization,
+ Problem,
+ ProblemType,
+ Profile,
+ Rating,
Submission,
)
from judge.utils.infinite_paginator import InfinitePaginationMixin
@@ -52,7 +61,11 @@ def __init__(self, lookup):
self.lookup = lookup
def to_filter(self, key_list):
- return {f'{self.lookup}_id__in': Language.objects.filter(key__in=key_list).values_list('id', flat=True)}
+ return {
+ f'{self.lookup}_id__in': Language.objects.filter(
+ key__in=key_list
+ ).values_list('id', flat=True)
+ }
class APILoginRequiredException(Exception):
@@ -101,10 +114,12 @@ def get_error(self, exception):
if exception_type in caught_exceptions:
status_code, message = caught_exceptions[exception_type]
return JsonResponse(
- self.get_base_response(error={
- 'code': status_code,
- 'message': message,
- }),
+ self.get_base_response(
+ error={
+ 'code': status_code,
+ 'message': message,
+ }
+ ),
status=status_code,
)
else:
@@ -146,23 +161,31 @@ def filter_queryset(self, queryset):
for key, filter_name in self.basic_filters:
if key in self.request.GET:
if isinstance(filter_name, BaseSimpleFilter):
- queryset = queryset.filter(**filter_name.to_filter(self.request.GET.get(key)))
+ queryset = queryset.filter(
+ **filter_name.to_filter(self.request.GET.get(key))
+ )
else:
# May raise ValueError or ValidationError, but is caught in APIMixin
- queryset = queryset.filter(**{
- filter_name: self.request.GET.get(key),
- })
+ queryset = queryset.filter(
+ **{
+ filter_name: self.request.GET.get(key),
+ }
+ )
self.used_basic_filters.add(key)
for key, filter_name in self.list_filters:
if key in self.request.GET:
if isinstance(filter_name, BaseListFilter):
- queryset = queryset.filter(**filter_name.to_filter(self.request.GET.getlist(key)))
+ queryset = queryset.filter(
+ **filter_name.to_filter(self.request.GET.getlist(key))
+ )
else:
# May raise ValueError or ValidationError, but is caught in APIMixin
- queryset = queryset.filter(**{
- filter_name + '__in': self.request.GET.getlist(key),
- })
+ queryset = queryset.filter(
+ **{
+ filter_name + '__in': self.request.GET.getlist(key),
+ }
+ )
self.used_list_filters.add(key)
return queryset
@@ -195,9 +218,7 @@ def get_api_data(self, context):
class APIContestList(APIListView):
model = Contest
- basic_filters = (
- ('is_rated', 'is_rated'),
- )
+ basic_filters = (('is_rated', 'is_rated'),)
list_filters = (
('key', 'key'),
('tag', 'tags__name'),
@@ -244,24 +265,23 @@ def get_object(self, queryset=None):
def get_object_data(self, contest):
in_contest = contest.is_in_contest(self.request.user)
can_see_rankings = contest.can_see_full_scoreboard(self.request.user)
- can_see_problems = (in_contest or contest.ended or contest.is_editable_by(self.request.user))
+ can_see_problems = (
+ in_contest or contest.ended or contest.is_editable_by(self.request.user)
+ )
problems = list(
- contest.contest_problems
- .select_related('problem')
+ contest.contest_problems.select_related('problem')
.defer('problem__description')
.order_by('order'),
)
new_ratings_subquery = Rating.objects.filter(participation=OuterRef('pk'))
- old_ratings_subquery = (
- Rating.objects
- .filter(user=OuterRef('user__pk'), contest__end_time__lt=OuterRef('contest__end_time'))
- .order_by('-contest__end_time')
- )
+ old_ratings_subquery = Rating.objects.filter(
+ user=OuterRef('user__pk'),
+ contest__end_time__lt=OuterRef('contest__end_time'),
+ ).order_by('-contest__end_time')
participations = (
- contest.users
- .filter(virtual=ContestParticipation.LIVE)
+ contest.users.filter(virtual=ContestParticipation.LIVE)
.annotate(
username=F('user__user__username'),
old_rating=Subquery(old_ratings_subquery.values('rating')[:1]),
@@ -285,13 +305,18 @@ def get_object_data(self, contest):
'has_rating': contest.ratings.exists(),
'rating_floor': contest.rating_floor,
'rating_ceiling': contest.rating_ceiling,
- 'hidden_scoreboard': contest.scoreboard_visibility in (contest.SCOREBOARD_AFTER_CONTEST,
- contest.SCOREBOARD_AFTER_PARTICIPATION,
- contest.SCOREBOARD_HIDDEN),
+ 'hidden_scoreboard': contest.scoreboard_visibility
+ in (
+ contest.SCOREBOARD_AFTER_CONTEST,
+ contest.SCOREBOARD_AFTER_PARTICIPATION,
+ contest.SCOREBOARD_HIDDEN,
+ ),
'scoreboard_visibility': contest.scoreboard_visibility,
'is_organization_private': contest.is_organization_private,
'organizations': list(
- contest.organizations.values_list('id', flat=True) if contest.is_organization_private else [],
+ contest.organizations.values_list('id', flat=True)
+ if contest.is_organization_private
+ else [],
),
'is_private': contest.is_private,
'tags': list(contest.tags.values_list('name', flat=True)),
@@ -308,8 +333,11 @@ def get_object_data(self, contest):
'label': contest.get_label_for_problem(index),
'name': problem.problem.name,
'code': problem.problem.code,
- } for index, problem in enumerate(problems)
- ] if can_see_problems else [],
+ }
+ for index, problem in enumerate(problems)
+ ]
+ if can_see_problems
+ else [],
'rankings': [
{
'user': participation.username,
@@ -321,9 +349,14 @@ def get_object_data(self, contest):
'old_rating': participation.old_rating,
'new_rating': participation.new_rating,
'is_disqualified': participation.is_disqualified,
- 'solutions': contest.format.get_problem_breakdown(participation, problems),
- } for participation in participations
- ] if can_see_rankings else [],
+ 'solutions': contest.format.get_problem_breakdown(
+ participation, problems
+ ),
+ }
+ for participation in participations
+ ]
+ if can_see_rankings
+ else [],
}
@@ -355,8 +388,9 @@ def get_unfiltered_queryset(self):
visible_contests = visible_contests.filter(q)
return (
- ContestParticipation.objects
- .filter(virtual__gte=0, contest__in=visible_contests)
+ ContestParticipation.objects.filter(
+ virtual__gte=0, contest__in=visible_contests
+ )
.select_related('user__user', 'contest')
.order_by('id')
.only(
@@ -390,9 +424,7 @@ def get_object_data(self, participation):
class APIProblemList(APIListView):
model = Problem
- basic_filters = (
- ('partial', 'partial'),
- )
+ basic_filters = (('partial', 'partial'),)
list_filters = (
('code', 'code'),
('group', 'group__full_name'),
@@ -443,7 +475,9 @@ class APIProblemDetail(APIDetailView):
def get_object(self, queryset=None):
problem = super().get_object(queryset)
- if not problem.is_accessible_by(self.request.user, skip_contest_problem_check=True):
+ if not problem.is_accessible_by(
+ self.request.user, skip_contest_problem_check=True
+ ):
raise Http404()
return problem
@@ -462,8 +496,9 @@ def get_object_data(self, problem):
'time_limit': time_limit,
'memory_limit': memory_limit,
}
- for key, time_limit, memory_limit in
- problem.language_limits.values_list('language__key', 'time_limit', 'memory_limit')
+ for key, time_limit, memory_limit in problem.language_limits.values_list(
+ 'language__key', 'time_limit', 'memory_limit'
+ )
],
'points': problem.points,
'partial': problem.partial,
@@ -471,7 +506,9 @@ def get_object_data(self, problem):
'languages': list(problem.allowed_languages.values_list('key', flat=True)),
'is_organization_private': problem.is_organization_private,
'organizations': list(
- problem.organizations.values_list('id', flat=True) if problem.is_organization_private else [],
+ problem.organizations.values_list('id', flat=True)
+ if problem.is_organization_private
+ else [],
),
'is_public': problem.is_public,
}
@@ -487,11 +524,17 @@ class APIUserList(APIListView):
def get_unfiltered_queryset(self):
return (
- Profile.objects
- .filter(is_unlisted=False, user__is_active=True)
+ Profile.objects.filter(is_unlisted=False, user__is_active=True)
.annotate(username=F('user__username'))
.order_by('id')
- .only('id', 'points', 'performance_points', 'problem_count', 'display_rank', 'rating')
+ .only(
+ 'id',
+ 'points',
+ 'performance_points',
+ 'problem_count',
+ 'display_rank',
+ 'rating',
+ )
)
def get_object_data(self, profile):
@@ -513,39 +556,49 @@ class APIUserDetail(APIDetailView):
def get_object_data(self, profile):
solved_problems = list(
- Submission.objects
- .filter(
+ Submission.objects.filter(
result='AC',
user=profile,
problem__is_public=True,
problem__is_organization_private=False,
)
- .values('problem').distinct()
+ .values('problem')
+ .distinct()
.values_list('problem__code', flat=True),
)
contest_history = []
- participations = (
- ContestParticipation.objects
- .filter(
- user=profile,
- virtual=ContestParticipation.LIVE,
- contest__in=Contest.get_visible_contests(self.request.user),
- contest__end_time__lt=self._now,
- )
- .order_by('contest__end_time')
- )
- for contest_key, score, cumtime, rating, mean, performance in participations.values_list(
- 'contest__key', 'score', 'cumtime', 'rating__rating', 'rating__mean', 'rating__performance',
+ participations = ContestParticipation.objects.filter(
+ user=profile,
+ virtual=ContestParticipation.LIVE,
+ contest__in=Contest.get_visible_contests(self.request.user),
+ contest__end_time__lt=self._now,
+ ).order_by('contest__end_time')
+ for (
+ contest_key,
+ score,
+ cumtime,
+ rating,
+ mean,
+ performance,
+ ) in participations.values_list(
+ 'contest__key',
+ 'score',
+ 'cumtime',
+ 'rating__rating',
+ 'rating__mean',
+ 'rating__performance',
):
- contest_history.append({
- 'key': contest_key,
- 'score': score,
- 'cumulative_time': cumtime,
- 'rating': rating,
- 'raw_rating': mean,
- 'performance': performance,
- })
+ contest_history.append(
+ {
+ 'key': contest_key,
+ 'score': score,
+ 'cumulative_time': cumtime,
+ 'rating': rating,
+ 'raw_rating': mean,
+ 'performance': performance,
+ }
+ )
return {
'id': profile.id,
@@ -582,15 +635,17 @@ def get_unfiltered_queryset(self):
use_straight_join(queryset)
join_sql_subquery(
queryset,
- subquery=Problem.get_visible_problems(self.request.user).distinct().only('id').query,
+ subquery=Problem.get_visible_problems(self.request.user)
+ .distinct()
+ .only('id')
+ .query,
params=[],
related_model=Problem,
join_fields=[('problem_id', 'id')],
alias='visible_problems',
)
return (
- queryset
- .select_related('problem', 'user__user', 'language')
+ queryset.select_related('problem', 'user__user', 'language')
.order_by('id')
.only(
'id',
@@ -642,7 +697,8 @@ def get_object_data(self, submission):
'memory': case.memory,
'points': case.points,
'total': case.total,
- } for case in batch['cases']
+ }
+ for case in batch['cases']
]
# These are individual cases.
@@ -650,13 +706,15 @@ def get_object_data(self, submission):
cases.extend(batch_cases)
# This is one batch.
else:
- cases.append({
- 'type': 'batch',
- 'batch_id': batch['id'],
- 'cases': batch_cases,
- 'points': batch['points'],
- 'total': batch['total'],
- })
+ cases.append(
+ {
+ 'type': 'batch',
+ 'batch_id': batch['id'],
+ 'cases': batch_cases,
+ 'points': batch['points'],
+ 'total': batch['total'],
+ }
+ )
return {
'id': submission.id,
@@ -677,15 +735,13 @@ def get_object_data(self, submission):
class APIOrganizationList(APIListView):
model = Organization
- basic_filters = (
- ('is_open', 'is_open'),
- )
- list_filters = (
- ('id', 'id'),
- )
+ basic_filters = (('is_open', 'is_open'),)
+ list_filters = (('id', 'id'),)
def get_unfiltered_queryset(self):
- return Organization.objects.annotate(member_count=Count('member')).order_by('id')
+ return Organization.objects.annotate(member_count=Count('member')).order_by(
+ 'id'
+ )
def get_object_data(self, organization):
return {
@@ -699,9 +755,7 @@ def get_object_data(self, organization):
class APILanguageList(APIListView):
model = Language
- basic_filters = (
- ('common_name', 'common_name'),
- )
+ basic_filters = (('common_name', 'common_name'),)
list_filters = (
('id', 'id'),
('key', 'key'),
@@ -723,7 +777,11 @@ class APIJudgeList(APIListView):
model = Judge
def get_unfiltered_queryset(self):
- return Judge.objects.filter(online=True).prefetch_related('runtimes').order_by('id')
+ return (
+ Judge.objects.filter(online=True)
+ .prefetch_related('runtimes')
+ .order_by('id')
+ )
def get_object_data(self, judge):
return {
diff --git a/judge/views/blog.py b/judge/views/blog.py
index d71d435552..0e311f2484 100644
--- a/judge/views/blog.py
+++ b/judge/views/blog.py
@@ -7,8 +7,17 @@
from django.views.generic import ListView
from judge.comments import CommentedDetailView
-from judge.models import BlogPost, Comment, Contest, Language, Problem, ProblemClarification, Profile, Submission, \
- Ticket
+from judge.models import (
+ BlogPost,
+ Comment,
+ Contest,
+ Language,
+ Problem,
+ ProblemClarification,
+ Profile,
+ Submission,
+ Ticket,
+)
from judge.utils.cachedict import CacheDict
from judge.utils.diggpaginator import DiggPaginator
from judge.utils.opengraph import generate_opengraph
@@ -23,66 +32,100 @@ class PostList(ListView):
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_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_queryset(self):
- return (BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now()).order_by('-sticky', '-publish_on')
- .prefetch_related('authors__user'))
+ return (
+ BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now())
+ .order_by('-sticky', '-publish_on')
+ .prefetch_related('authors__user')
+ )
def get_context_data(self, **kwargs):
context = super(PostList, self).get_context_data(**kwargs)
- context['title'] = self.title or _('Page %d of Posts') % context['page_obj'].number
+ 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['comments'] = Comment.most_recent(self.request.user, 10)
- context['new_problems'] = Problem.get_public_problems() \
- .order_by('-date', 'code')[:settings.DMOJ_BLOG_NEW_PROBLEM_COUNT]
+ context['new_problems'] = Problem.get_public_problems().order_by(
+ '-date', 'code'
+ )[: settings.DMOJ_BLOG_NEW_PROBLEM_COUNT]
context['page_titles'] = CacheDict(lambda page: Comment.get_page_title(page))
context['has_clarifications'] = False
if self.request.user.is_authenticated:
participation = self.request.profile.current_contest
if participation:
- clarifications = ProblemClarification.objects.filter(problem__in=participation.contest.problems.all())
+ clarifications = ProblemClarification.objects.filter(
+ problem__in=participation.contest.problems.all()
+ )
context['has_clarifications'] = clarifications.count() > 0
context['clarifications'] = clarifications.order_by('-date')
context['user_count'] = Profile.objects.count
context['problem_count'] = Problem.get_public_problems().count
- context['submission_count'] = lambda: Submission.objects.aggregate(max_id=Max('id'))['max_id'] or 0
+ context['submission_count'] = (
+ lambda: Submission.objects.aggregate(max_id=Max('id'))['max_id'] or 0
+ )
context['language_count'] = Language.objects.count
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()
+ 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) \
- .order_by('start_time')
+ visible_contests = (
+ Contest.get_visible_contests(self.request.user)
+ .filter(is_visible=True)
+ .order_by('start_time')
+ )
- context['current_contests'] = visible_contests.filter(start_time__lte=now, end_time__gt=now)
+ context['current_contests'] = visible_contests.filter(
+ start_time__lte=now, end_time__gt=now
+ )
context['future_contests'] = visible_contests.filter(start_time__gt=now)
if self.request.user.is_authenticated:
context['own_open_tickets'] = (
- Ticket.objects.filter(user=self.request.profile, is_open=True).order_by('-id')
- .prefetch_related('linked_item').select_related('user__user')
+ Ticket.objects.filter(user=self.request.profile, is_open=True)
+ .order_by('-id')
+ .prefetch_related('linked_item')
+ .select_related('user__user')
)
else:
context['own_open_tickets'] = []
# Superusers better be staffs, not the spell-casting kind either.
if self.request.user.is_staff:
- tickets = (Ticket.objects.order_by('-id').filter(is_open=True).prefetch_related('linked_item')
- .select_related('user__user'))
- context['open_tickets'] = filter_visible_tickets(tickets, self.request.user)[:10]
+ tickets = (
+ Ticket.objects.order_by('-id')
+ .filter(is_open=True)
+ .prefetch_related('linked_item')
+ .select_related('user__user')
+ )
+ context['open_tickets'] = filter_visible_tickets(
+ tickets, self.request.user
+ )[:10]
else:
context['open_tickets'] = []
return context
@@ -103,8 +146,11 @@ def get_comment_page(self):
def get_context_data(self, **kwargs):
context = super(PostView, self).get_context_data(**kwargs)
- metadata = generate_opengraph('generated-meta-blog:%d' % self.object.id,
- self.object.summary or self.object.content, 'blog')
+ metadata = generate_opengraph(
+ 'generated-meta-blog:%d' % self.object.id,
+ self.object.summary or self.object.content,
+ 'blog',
+ )
context['meta_description'] = metadata[0]
context['og_image'] = self.object.og_image or metadata[1]
diff --git a/judge/views/comment.py b/judge/views/comment.py
index 7b2e086a7e..ecfe641fa0 100644
--- a/judge/views/comment.py
+++ b/judge/views/comment.py
@@ -4,8 +4,14 @@
from django.db import IntegrityError
from django.db.models import F
from django.forms.models import ModelForm
-from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotFound, \
- HttpResponseRedirect
+from django.http import (
+ Http404,
+ HttpResponse,
+ HttpResponseBadRequest,
+ HttpResponseForbidden,
+ HttpResponseNotFound,
+ HttpResponseRedirect,
+)
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext as _
from django.views.decorators.http import require_POST
@@ -18,14 +24,21 @@
from judge.utils.views import TitleMixin
from judge.widgets import MathJaxPagedownWidget
-__all__ = ['upvote_comment', 'downvote_comment', 'CommentEditAjax', 'CommentContent',
- 'CommentEdit']
+__all__ = [
+ 'upvote_comment',
+ 'downvote_comment',
+ 'CommentEditAjax',
+ 'CommentContent',
+ 'CommentEdit',
+]
@login_required
def vote_comment(request, delta):
if abs(delta) != 1:
- return HttpResponseBadRequest(_('Messing around, are we?'), content_type='text/plain')
+ return HttpResponseBadRequest(
+ _('Messing around, are we?'), content_type='text/plain'
+ )
if request.method != 'POST':
return HttpResponseForbidden()
@@ -34,11 +47,15 @@ def vote_comment(request, delta):
return HttpResponseBadRequest()
if not request.user.is_staff and not request.profile.has_any_solves:
- return HttpResponseBadRequest(_('You must solve at least one problem before you can vote.'),
- content_type='text/plain')
+ return HttpResponseBadRequest(
+ _('You must solve at least one problem before you can vote.'),
+ content_type='text/plain',
+ )
if request.profile.mute:
- return HttpResponseBadRequest(_('Your part is silent, little toad.'), content_type='text/plain')
+ return HttpResponseBadRequest(
+ _('Your part is silent, little toad.'), content_type='text/plain'
+ )
try:
comment_id = int(request.POST['id'])
@@ -51,7 +68,9 @@ def vote_comment(request, delta):
return HttpResponseNotFound(_('Comment not found.'), content_type='text/plain')
if comment.author == request.profile:
- return HttpResponseBadRequest(_('You cannot vote on your own comments.'), content_type='text/plain')
+ return HttpResponseBadRequest(
+ _('You cannot vote on your own comments.'), content_type='text/plain'
+ )
vote = CommentVote()
vote.comment_id = comment_id
@@ -64,12 +83,16 @@ def vote_comment(request, delta):
except IntegrityError:
with LockModel(write=(CommentVote,)):
try:
- vote = CommentVote.objects.get(comment_id=comment_id, voter=request.profile)
+ vote = CommentVote.objects.get(
+ comment_id=comment_id, voter=request.profile
+ )
except CommentVote.DoesNotExist:
# We must continue racing in case this is exploited to manipulate votes.
continue
if -vote.score != delta:
- return HttpResponseBadRequest(_('You already voted.'), content_type='text/plain')
+ return HttpResponseBadRequest(
+ _('You already voted.'), content_type='text/plain'
+ )
vote.delete()
Comment.objects.filter(id=comment_id).update(score=F('score') - vote.score)
else:
@@ -105,7 +128,9 @@ def get_context_data(self, **kwargs):
context = super(CommentRevisionAjax, self).get_context_data(**kwargs)
revisions = Version.objects.get_for_object(self.object).order_by('-revision')
try:
- wanted = min(max(int(self.request.GET.get('revision', 0)), 0), len(revisions) - 1)
+ wanted = min(
+ max(int(self.request.GET.get('revision', 0)), 0), len(revisions) - 1
+ )
except ValueError:
raise Http404
context['revision'] = revisions[wanted]
@@ -123,7 +148,9 @@ class Meta:
model = Comment
fields = ['body']
if MathJaxPagedownWidget is not None:
- widgets = {'body': MathJaxPagedownWidget(attrs={'id': 'id-edit-comment-body'})}
+ widgets = {
+ 'body': MathJaxPagedownWidget(attrs={'id': 'id-edit-comment-body'})
+ }
class CommentEditAjax(LoginRequiredMixin, CommentMixin, UpdateView):
@@ -170,8 +197,9 @@ class CommentVotesAjax(PermissionRequiredMixin, CommentMixin, DetailView):
def get_context_data(self, **kwargs):
context = super(CommentVotesAjax, self).get_context_data(**kwargs)
- context['votes'] = (self.object.votes.select_related('voter__user')
- .only('id', 'voter__display_rank', 'voter__user__username', 'score'))
+ context['votes'] = self.object.votes.select_related('voter__user').only(
+ 'id', 'voter__display_rank', 'voter__user__username', 'score'
+ )
return context
diff --git a/judge/views/contests.py b/judge/views/contests.py
index 880e128f7e..4b90c9d72a 100644
--- a/judge/views/contests.py
+++ b/judge/views/contests.py
@@ -11,9 +11,27 @@
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
from django.db import IntegrityError
-from django.db.models import BooleanField, Case, Count, F, FloatField, IntegerField, Max, Min, Q, Sum, Value, When
+from django.db.models import (
+ BooleanField,
+ Case,
+ Count,
+ F,
+ FloatField,
+ IntegerField,
+ Max,
+ Min,
+ Q,
+ Sum,
+ Value,
+ When,
+)
from django.db.models.expressions import CombinedExpression, Exists, OuterRef
-from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
+from django.http import (
+ Http404,
+ HttpResponse,
+ HttpResponseBadRequest,
+ HttpResponseRedirect,
+)
from django.shortcuts import get_object_or_404, render
from django.template.defaultfilters import date as date_filter
from django.urls import reverse
@@ -32,21 +50,47 @@
from judge import event_poster as event
from judge.comments import CommentedDetailView
from judge.forms import ContestCloneForm
-from judge.models import Contest, ContestMoss, ContestParticipation, ContestProblem, ContestTag, \
- Problem, Profile, Submission
+from judge.models import (
+ Contest,
+ ContestMoss,
+ ContestParticipation,
+ ContestProblem,
+ ContestTag,
+ Problem,
+ Profile,
+ Submission,
+)
from judge.tasks import run_moss
from judge.utils.celery import redirect_to_task_status
from judge.utils.opengraph import generate_opengraph
from judge.utils.problems import _get_result_data
from judge.utils.ranker import ranker
from judge.utils.stats import get_bar_chart, get_pie_chart
-from judge.utils.views import DiggPaginatorMixin, QueryStringSortMixin, SingleObjectFormView, TitleMixin, \
- generic_message
+from judge.utils.views import (
+ DiggPaginatorMixin,
+ QueryStringSortMixin,
+ SingleObjectFormView,
+ TitleMixin,
+ generic_message,
+)
-__all__ = ['ContestList', 'ContestDetail', 'ContestRanking', 'ContestJoin', 'ContestLeave', 'ContestCalendar',
- 'ContestClone', 'ContestStats', 'ContestMossView', 'ContestMossDelete', 'contest_ranking_ajax',
- 'ContestParticipationList', 'ContestParticipationDisqualify', 'get_contest_ranking_list',
- 'base_contest_ranking_list']
+__all__ = [
+ 'ContestList',
+ 'ContestDetail',
+ 'ContestRanking',
+ 'ContestJoin',
+ 'ContestLeave',
+ 'ContestCalendar',
+ 'ContestClone',
+ 'ContestStats',
+ 'ContestMossView',
+ 'ContestMossDelete',
+ 'contest_ranking_ajax',
+ 'ContestParticipationList',
+ 'ContestParticipationDisqualify',
+ 'get_contest_ranking_list',
+ 'base_contest_ranking_list',
+]
def _find_contest(request, key, private_check=True):
@@ -55,8 +99,15 @@ def _find_contest(request, key, private_check=True):
if private_check and not contest.is_accessible_by(request.user):
raise ObjectDoesNotExist()
except ObjectDoesNotExist:
- return generic_message(request, _('No such contest'),
- _('Could not find a contest with the key "%s".') % key, status=404), False
+ return (
+ generic_message(
+ request,
+ _('No such contest'),
+ _('Could not find a contest with the key "%s".') % key,
+ status=404,
+ ),
+ False,
+ )
return contest, True
@@ -65,7 +116,9 @@ def get_queryset(self):
return Contest.get_visible_contests(self.request.user)
-class ContestList(QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ContestListMixin, ListView):
+class ContestList(
+ QueryStringSortMixin, DiggPaginatorMixin, TitleMixin, ContestListMixin, ListView
+):
model = Contest
paginate_by = 20
template_name = 'contest/list.html'
@@ -80,14 +133,18 @@ def _now(self):
return timezone.now()
def _get_queryset(self):
- queryset = super().get_queryset().prefetch_related(
- 'tags',
- 'organizations',
- 'authors',
- 'curators',
- 'testers',
- 'spectators',
- 'classes',
+ queryset = (
+ super()
+ .get_queryset()
+ .prefetch_related(
+ 'tags',
+ 'organizations',
+ 'authors',
+ 'curators',
+ 'testers',
+ 'spectators',
+ 'classes',
+ )
)
profile = self.request.profile
@@ -95,19 +152,52 @@ def _get_queryset(self):
return queryset
return queryset.annotate(
- editor_or_tester=Exists(Contest.authors.through.objects.filter(contest=OuterRef('pk'), profile=profile))
- .bitor(Exists(Contest.curators.through.objects.filter(contest=OuterRef('pk'), profile=profile)))
- .bitor(Exists(Contest.testers.through.objects.filter(contest=OuterRef('pk'), profile=profile))),
- completed_contest=Exists(ContestParticipation.objects.filter(contest=OuterRef('pk'), user=profile,
- virtual=ContestParticipation.LIVE)),
+ editor_or_tester=Exists(
+ Contest.authors.through.objects.filter(
+ contest=OuterRef('pk'), profile=profile
+ )
+ )
+ .bitor(
+ Exists(
+ Contest.curators.through.objects.filter(
+ contest=OuterRef('pk'), profile=profile
+ )
+ )
+ )
+ .bitor(
+ Exists(
+ Contest.testers.through.objects.filter(
+ contest=OuterRef('pk'), profile=profile
+ )
+ )
+ ),
+ completed_contest=Exists(
+ ContestParticipation.objects.filter(
+ contest=OuterRef('pk'),
+ user=profile,
+ virtual=ContestParticipation.LIVE,
+ )
+ ),
)
def get_queryset(self):
- return self._get_queryset().order_by(self.order, 'key').filter(end_time__lt=self._now)
+ return (
+ self._get_queryset()
+ .order_by(self.order, 'key')
+ .filter(end_time__lt=self._now)
+ )
- def get_paginator(self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs):
- return super().get_paginator(queryset, per_page, orphans, allow_empty_first_page,
- count=self.get_queryset().values('id').count(), **kwargs)
+ def get_paginator(
+ self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
+ ):
+ return super().get_paginator(
+ queryset,
+ per_page,
+ orphans,
+ allow_empty_first_page,
+ count=self.get_queryset().values('id').count(),
+ **kwargs,
+ )
def get_context_data(self, **kwargs):
context = super(ContestList, self).get_context_data(**kwargs)
@@ -121,9 +211,16 @@ def get_context_data(self, **kwargs):
if self.request.user.is_authenticated:
for participation in (
- ContestParticipation.objects.filter(virtual=0, user=self.request.profile, contest_id__in=present)
+ ContestParticipation.objects.filter(
+ virtual=0, user=self.request.profile, contest_id__in=present
+ )
.select_related('contest')
- .prefetch_related('contest__authors', 'contest__curators', 'contest__testers', 'contest__spectators')
+ .prefetch_related(
+ 'contest__authors',
+ 'contest__curators',
+ 'contest__testers',
+ 'contest__spectators',
+ )
.annotate(key=F('contest__key'))
):
if participation.ended:
@@ -188,11 +285,11 @@ def get_context_data(self, **kwargs):
context = super(ContestMixin, self).get_context_data(**kwargs)
if self.request.user.is_authenticated:
try:
- context['live_participation'] = (
- self.request.profile.contest_history.get(
- contest=self.object,
- virtual=ContestParticipation.LIVE,
- )
+ context[
+ 'live_participation'
+ ] = self.request.profile.contest_history.get(
+ contest=self.object,
+ virtual=ContestParticipation.LIVE,
)
except ContestParticipation.DoesNotExist:
context['live_participation'] = None
@@ -210,14 +307,22 @@ def get_context_data(self, **kwargs):
context['can_edit'] = self.can_edit
if not self.object.og_image or not self.object.summary:
- metadata = generate_opengraph('generated-meta-contest:%d' % self.object.id,
- self.object.description, 'contest')
+ metadata = generate_opengraph(
+ 'generated-meta-contest:%d' % self.object.id,
+ self.object.description,
+ 'contest',
+ )
context['meta_description'] = self.object.summary or metadata[0]
context['og_image'] = self.object.og_image or metadata[1]
context['has_moss_api_key'] = settings.MOSS_API_KEY is not None
context['logo_override_image'] = self.object.logo_override_image
- if not context['logo_override_image'] and self.object.organizations.count() == 1:
- context['logo_override_image'] = self.object.organizations.first().logo_override_image
+ if (
+ not context['logo_override_image']
+ and self.object.organizations.count() == 1
+ ):
+ context[
+ 'logo_override_image'
+ ] = self.object.organizations.first().logo_override_image
return context
@@ -225,15 +330,24 @@ def get_object(self, queryset=None):
contest = super(ContestMixin, self).get_object(queryset)
profile = self.request.profile
- if (profile is not None and
- ContestParticipation.objects.filter(id=profile.current_contest_id, contest_id=contest.id).exists()):
+ if (
+ profile is not None
+ and ContestParticipation.objects.filter(
+ id=profile.current_contest_id, contest_id=contest.id
+ ).exists()
+ ):
return contest
try:
contest.access_check(self.request.user)
except Contest.PrivateContest:
- raise PrivateContestError(contest.name, contest.is_private, contest.is_organization_private,
- contest.organizations.all(), contest.classes.all())
+ raise PrivateContestError(
+ contest.name,
+ contest.is_private,
+ contest.is_organization_private,
+ contest.organizations.all(),
+ contest.classes.all(),
+ )
except Contest.Inaccessible:
raise Http404()
else:
@@ -245,15 +359,25 @@ def dispatch(self, request, *args, **kwargs):
except Http404:
key = kwargs.get(self.slug_url_kwarg, None)
if key:
- return generic_message(request, _('No such contest'),
- _('Could not find a contest with the key "%s".') % key)
+ return generic_message(
+ request,
+ _('No such contest'),
+ _('Could not find a contest with the key "%s".') % key,
+ )
else:
- return generic_message(request, _('No such contest'),
- _('Could not find such contest.'))
+ return generic_message(
+ request, _('No such contest'), _('Could not find such contest.')
+ )
except PrivateContestError as e:
- return render(request, 'contest/private.html', {
- 'error': e, 'title': _('Access to contest "%s" denied') % e.name,
- }, status=403)
+ return render(
+ request,
+ 'contest/private.html',
+ {
+ 'error': e,
+ 'title': _('Access to contest "%s" denied') % e.name,
+ },
+ status=403,
+ )
class ContestDetail(ContestMixin, TitleMixin, CommentedDetailView):
@@ -267,26 +391,36 @@ def get_title(self):
def get_context_data(self, **kwargs):
context = super(ContestDetail, self).get_context_data(**kwargs)
- context['contest_problems'] = Problem.objects.filter(contests__contest=self.object) \
- .order_by('contests__order').defer('description') \
- .annotate(has_public_editorial=Case(
- When(solution__is_public=True, solution__publish_on__lte=timezone.now(), then=True),
- default=False,
- output_field=BooleanField(),
- )) \
+ context['contest_problems'] = (
+ Problem.objects.filter(contests__contest=self.object)
+ .order_by('contests__order')
+ .defer('description')
+ .annotate(
+ has_public_editorial=Case(
+ When(
+ solution__is_public=True,
+ solution__publish_on__lte=timezone.now(),
+ then=True,
+ ),
+ default=False,
+ output_field=BooleanField(),
+ )
+ )
.add_i18n_name(self.request.LANGUAGE_CODE)
+ )
context['metadata'] = {
'has_public_editorials': any(
- problem.is_public and problem.has_public_editorial for problem in context['contest_problems']
+ problem.is_public and problem.has_public_editorial
+ for problem in context['contest_problems']
),
}
context['metadata'].update(
- **self.object.contest_problems
- .annotate(
+ **self.object.contest_problems.annotate(
partials_enabled=F('partial').bitand(F('problem__partial')),
- pretests_enabled=F('is_pretested').bitand(F('contest__run_pretests_only')),
- )
- .aggregate(
+ pretests_enabled=F('is_pretested').bitand(
+ F('contest__run_pretests_only')
+ ),
+ ).aggregate(
has_partials=Sum('partials_enabled'),
has_pretests=Sum('pretests_enabled'),
has_submission_cap=Sum('max_submissions'),
@@ -296,7 +430,9 @@ def get_context_data(self, **kwargs):
return context
-class ContestClone(ContestMixin, PermissionRequiredMixin, TitleMixin, SingleObjectFormView):
+class ContestClone(
+ ContestMixin, PermissionRequiredMixin, TitleMixin, SingleObjectFormView
+):
title = gettext_lazy('Clone Contest')
template_name = 'contest/clone.html'
form_class = ContestCloneForm
@@ -333,7 +469,9 @@ def form_valid(self, form):
revisions.set_user(self.request.user)
revisions.set_comment(_('Cloned contest from %s') % old_key)
- return HttpResponseRedirect(reverse('admin:judge_contest_change', args=(contest.id,)))
+ return HttpResponseRedirect(
+ reverse('admin:judge_contest_change', args=(contest.id,))
+ )
class ContestAccessDenied(Exception):
@@ -367,27 +505,52 @@ def join_contest(self, request, access_code=None):
contest = self.object
if not contest.started and not (self.is_editor or self.is_tester):
- return generic_message(request, _('Contest not ongoing'),
- _('"%s" is not currently ongoing.') % contest.name)
+ return generic_message(
+ request,
+ _('Contest not ongoing'),
+ _('"%s" is not currently ongoing.') % contest.name,
+ )
profile = request.profile
- if not request.user.is_superuser and contest.banned_users.filter(id=profile.id).exists():
- return generic_message(request, _('Banned from joining'),
- _('You have been declared persona non grata for this contest. '
- 'You are permanently barred from joining this contest.'))
+ if (
+ not request.user.is_superuser
+ and contest.banned_users.filter(id=profile.id).exists()
+ ):
+ return generic_message(
+ request,
+ _('Banned from joining'),
+ _(
+ 'You have been declared persona non grata for this contest. '
+ 'You are permanently barred from joining this contest.'
+ ),
+ )
- requires_access_code = (not self.can_edit and contest.access_code and access_code != contest.access_code)
+ requires_access_code = (
+ not self.can_edit
+ and contest.access_code
+ and access_code != contest.access_code
+ )
if contest.ended:
if requires_access_code:
raise ContestAccessDenied()
while True:
- virtual_id = max((ContestParticipation.objects.filter(contest=contest, user=profile)
- .aggregate(virtual_id=Max('virtual'))['virtual_id'] or 0) + 1, 1)
+ virtual_id = max(
+ (
+ ContestParticipation.objects.filter(
+ contest=contest, user=profile
+ ).aggregate(virtual_id=Max('virtual'))['virtual_id']
+ or 0
+ )
+ + 1,
+ 1,
+ )
try:
participation = ContestParticipation.objects.create(
- contest=contest, user=profile, virtual=virtual_id,
+ contest=contest,
+ user=profile,
+ virtual=virtual_id,
real_start=timezone.now(),
)
# There is obviously a race condition here, so we keep trying until we win the race.
@@ -404,24 +567,33 @@ def join_contest(self, request, access_code=None):
elif contest.is_spectatable_by(request.user):
participation_type = SPECTATE
else:
- return generic_message(request, _('Cannot enter'),
- _('You are not able to join this contest.'))
+ return generic_message(
+ request,
+ _('Cannot enter'),
+ _('You are not able to join this contest.'),
+ )
try:
participation = ContestParticipation.objects.get(
- contest=contest, user=profile, virtual=participation_type,
+ contest=contest,
+ user=profile,
+ virtual=participation_type,
)
except ContestParticipation.DoesNotExist:
if requires_access_code:
raise ContestAccessDenied()
participation = ContestParticipation.objects.create(
- contest=contest, user=profile, virtual=participation_type,
+ contest=contest,
+ user=profile,
+ virtual=participation_type,
real_start=timezone.now(),
)
else:
if participation.ended:
participation = ContestParticipation.objects.get_or_create(
- contest=contest, user=profile, virtual=SPECTATE,
+ contest=contest,
+ user=profile,
+ virtual=SPECTATE,
defaults={'real_start': timezone.now()},
)[0]
@@ -437,14 +609,21 @@ def ask_for_access_code(self, form=None):
if form:
if form.is_valid():
if form.cleaned_data['access_code'] == contest.access_code:
- return self.join_contest(self.request, form.cleaned_data['access_code'])
+ return self.join_contest(
+ self.request, form.cleaned_data['access_code']
+ )
wrong_code = True
else:
form = ContestAccessCodeForm()
- return render(self.request, 'contest/access_code.html', {
- 'form': form, 'wrong_code': wrong_code,
- 'title': _('Enter access code for "%s"') % contest.name,
- })
+ return render(
+ self.request,
+ 'contest/access_code.html',
+ {
+ 'form': form,
+ 'wrong_code': wrong_code,
+ 'title': _('Enter access code for "%s"') % contest.name,
+ },
+ )
class ContestLeave(LoginRequiredMixin, ContestMixin, SingleObjectMixin, View):
@@ -452,9 +631,16 @@ def post(self, request, *args, **kwargs):
contest = self.get_object()
profile = request.profile
- if profile.current_contest is None or profile.current_contest.contest_id != contest.id:
- return generic_message(request, _('No such contest'),
- _('You are not in contest "%s".') % contest.key, 404)
+ if (
+ profile.current_contest is None
+ or profile.current_contest.contest_id != contest.id
+ ):
+ return generic_message(
+ request,
+ _('No such contest'),
+ _('You are not in contest "%s".') % contest.key,
+ 404,
+ )
profile.remove_contest()
return HttpResponseRedirect(reverse('contest_view', args=(contest.key,)))
@@ -472,7 +658,9 @@ def get(self, request, *args, **kwargs):
self.year = int(kwargs['year'])
self.month = int(kwargs['month'])
except (KeyError, ValueError):
- raise ImproperlyConfigured('ContestCalendar requires integer year and month')
+ raise ImproperlyConfigured(
+ 'ContestCalendar requires integer year and month'
+ )
self.today = timezone.now().date()
return self.render()
@@ -482,12 +670,16 @@ def render(self):
def get_contest_data(self, start, end):
end += timedelta(days=1)
- contests = self.get_queryset().filter(Q(start_time__gte=start, start_time__lt=end) |
- Q(end_time__gte=start, end_time__lt=end))
+ contests = self.get_queryset().filter(
+ Q(start_time__gte=start, start_time__lt=end)
+ | Q(end_time__gte=start, end_time__lt=end)
+ )
starts, ends, oneday = (defaultdict(list) for i in range(3))
for contest in contests:
start_date = timezone.localtime(contest.start_time).date()
- end_date = timezone.localtime(contest.end_time - timedelta(seconds=1)).date()
+ end_date = timezone.localtime(
+ contest.end_time - timedelta(seconds=1)
+ ).date()
if start_date == end_date:
oneday[start_date].append(contest)
else:
@@ -497,12 +689,24 @@ def get_contest_data(self, start, end):
def get_table(self):
calendar = Calendar(self.firstweekday).monthdatescalendar(self.year, self.month)
- starts, ends, oneday = self.get_contest_data(make_aware(datetime.combine(calendar[0][0], time.min)),
- make_aware(datetime.combine(calendar[-1][-1], time.min)))
- return [[ContestDay(
- date=date, is_pad=date.month != self.month,
- is_today=date == self.today, starts=starts[date], ends=ends[date], oneday=oneday[date],
- ) for date in week] for week in calendar]
+ starts, ends, oneday = self.get_contest_data(
+ make_aware(datetime.combine(calendar[0][0], time.min)),
+ make_aware(datetime.combine(calendar[-1][-1], time.min)),
+ )
+ return [
+ [
+ ContestDay(
+ date=date,
+ is_pad=date.month != self.month,
+ is_today=date == self.today,
+ starts=starts[date],
+ ends=ends[date],
+ oneday=oneday[date],
+ )
+ for date in week
+ ]
+ for week in calendar
+ ]
def get_context_data(self, **kwargs):
context = super(ContestCalendar, self).get_context_data(**kwargs)
@@ -512,7 +716,9 @@ def get_context_data(self, **kwargs):
except ValueError:
raise Http404()
else:
- context['title'] = _('Contests in %(month)s') % {'month': date_filter(month, _('F Y'))}
+ context['title'] = _('Contests in %(month)s') % {
+ 'month': date_filter(month, _('F Y'))
+ }
dates = Contest.objects.aggregate(min=Min('start_time'), max=Max('end_time'))
min_month = (self.today.year, self.today.month)
@@ -520,7 +726,10 @@ def get_context_data(self, **kwargs):
min_month = dates['min'].year, dates['min'].month
max_month = (self.today.year, self.today.month)
if dates['max'] is not None:
- max_month = max((dates['max'].year, dates['max'].month), (self.today.year, self.today.month))
+ max_month = max(
+ (dates['max'].year, dates['max'].month),
+ (self.today.year, self.today.month),
+ )
month = (self.year, self.month)
if month < min_month or month > max_month:
@@ -532,12 +741,20 @@ def get_context_data(self, **kwargs):
context['curr_month'] = date(self.year, self.month, 1)
if month > min_month:
- context['prev_month'] = date(self.year - (self.month == 1), 12 if self.month == 1 else self.month - 1, 1)
+ context['prev_month'] = date(
+ self.year - (self.month == 1),
+ 12 if self.month == 1 else self.month - 1,
+ 1,
+ )
else:
context['prev_month'] = None
if month < max_month:
- context['next_month'] = date(self.year + (self.month == 12), 1 if self.month == 12 else self.month + 1, 1)
+ context['next_month'] = date(
+ self.year + (self.month == 12),
+ 1 if self.month == 12 else self.month + 1,
+ 1,
+ )
else:
context['next_month'] = None
return context
@@ -555,7 +772,9 @@ def generate_ical(self):
event = Event()
event.add('uid', f'contest-{contest.key}@{domain}')
event.add('summary', contest.name)
- event.add('location', self.request.build_absolute_uri(contest.get_absolute_url()))
+ event.add(
+ 'location', self.request.build_absolute_uri(contest.get_absolute_url())
+ )
event.add('dtstart', contest.start_time.astimezone(timezone.utc))
event.add('dtend', contest.end_time.astimezone(timezone.utc))
event.add('dtstamp', now)
@@ -580,15 +799,22 @@ def get_context_data(self, **kwargs):
queryset = Submission.objects.filter(contest_object=self.object)
- ac_count = Count(Case(When(result='AC', then=Value(1)), output_field=IntegerField()))
- ac_rate = CombinedExpression(ac_count / Count('problem'), '*', Value(100.0), output_field=FloatField())
+ ac_count = Count(
+ Case(When(result='AC', then=Value(1)), output_field=IntegerField())
+ )
+ ac_rate = CombinedExpression(
+ ac_count / Count('problem'), '*', Value(100.0), output_field=FloatField()
+ )
status_count_queryset = list(
- queryset.values('problem__code', 'result').annotate(count=Count('result'))
- .values_list('problem__code', 'result', 'count'),
+ queryset.values('problem__code', 'result')
+ .annotate(count=Count('result'))
+ .values_list('problem__code', 'result', 'count'),
)
labels, codes = [], []
- contest_problems = self.object.contest_problems.order_by('order').values_list('problem__name', 'problem__code')
+ contest_problems = self.object.contest_problems.order_by('order').values_list(
+ 'problem__name', 'problem__code'
+ )
if contest_problems:
labels, codes = zip(*contest_problems)
num_problems = len(labels)
@@ -599,7 +825,9 @@ def get_context_data(self, **kwargs):
result_data = defaultdict(partial(list, [0] * num_problems))
for i in range(num_problems):
- for category in _get_result_data(defaultdict(int, status_counts[i]))['categories']:
+ for category in _get_result_data(defaultdict(int, status_counts[i]))[
+ 'categories'
+ ]:
result_data[category['code']][i] = category['count']
stats = {
@@ -608,23 +836,32 @@ def get_context_data(self, **kwargs):
'datasets': [
{
'label': name,
- 'backgroundColor': settings.DMOJ_STATS_SUBMISSION_RESULT_COLORS[name],
+ 'backgroundColor': settings.DMOJ_STATS_SUBMISSION_RESULT_COLORS[
+ name
+ ],
'data': data,
}
for name, data in result_data.items()
],
},
'problem_ac_rate': get_bar_chart(
- queryset.values('contest__problem__order', 'problem__name').annotate(ac_rate=ac_rate)
- .order_by('contest__problem__order').values_list('problem__name', 'ac_rate'),
+ queryset.values('contest__problem__order', 'problem__name')
+ .annotate(ac_rate=ac_rate)
+ .order_by('contest__problem__order')
+ .values_list('problem__name', 'ac_rate'),
),
'language_count': get_pie_chart(
- queryset.values('language__name').annotate(count=Count('language__name'))
- .filter(count__gt=0).order_by('-count').values_list('language__name', 'count'),
+ queryset.values('language__name')
+ .annotate(count=Count('language__name'))
+ .filter(count__gt=0)
+ .order_by('-count')
+ .values_list('language__name', 'count'),
),
'language_ac_rate': get_bar_chart(
- queryset.values('language__name').annotate(ac_rate=ac_rate)
- .filter(ac_rate__gt=0).values_list('language__name', 'ac_rate'),
+ queryset.values('language__name')
+ .annotate(ac_rate=ac_rate)
+ .filter(ac_rate__gt=0)
+ .values_list('language__name', 'ac_rate'),
),
}
@@ -661,8 +898,13 @@ def display_user_problem(contest_problem):
cumtime=participation.cumtime,
tiebreaker=participation.tiebreaker,
organization=user.organization,
- participation_rating=participation.rating.rating if hasattr(participation, 'rating') else None,
- problem_cells=[display_user_problem(contest_problem) for contest_problem in contest_problems],
+ participation_rating=participation.rating.rating
+ if hasattr(participation, 'rating')
+ else None,
+ problem_cells=[
+ display_user_problem(contest_problem)
+ for contest_problem in contest_problems
+ ],
result_cell=contest.format.display_participation_result(participation),
participation=participation,
display_name=user.display_name,
@@ -670,22 +912,45 @@ def display_user_problem(contest_problem):
def base_contest_ranking_list(contest, problems, queryset):
- return [make_contest_ranking_profile(contest, participation, problems) for participation in
- queryset.select_related('user__user', 'rating').defer('user__about', 'user__organizations__about')]
+ return [
+ make_contest_ranking_profile(contest, participation, problems)
+ for participation in queryset.select_related('user__user', 'rating').defer(
+ 'user__about', 'user__organizations__about'
+ )
+ ]
def contest_ranking_list(contest, problems):
- return base_contest_ranking_list(contest, problems, contest.users.filter(virtual=0)
- .prefetch_related('user__organizations')
- .annotate(submission_cnt=Count('submission'))
- .order_by('is_disqualified', '-score', 'cumtime', 'tiebreaker', '-submission_cnt'))
+ return base_contest_ranking_list(
+ contest,
+ problems,
+ contest.users.filter(virtual=0)
+ .prefetch_related('user__organizations')
+ .annotate(submission_cnt=Count('submission'))
+ .order_by(
+ 'is_disqualified', '-score', 'cumtime', 'tiebreaker', '-submission_cnt'
+ ),
+ )
-def get_contest_ranking_list(request, contest, participation=None, ranking_list=contest_ranking_list,
- show_current_virtual=True, ranker=ranker):
- problems = list(contest.contest_problems.select_related('problem').defer('problem__description').order_by('order'))
+def get_contest_ranking_list(
+ request,
+ contest,
+ participation=None,
+ ranking_list=contest_ranking_list,
+ show_current_virtual=True,
+ ranker=ranker,
+):
+ problems = list(
+ contest.contest_problems.select_related('problem')
+ .defer('problem__description')
+ .order_by('order')
+ )
- users = ranker(ranking_list(contest, problems), key=attrgetter('points', 'cumtime', 'tiebreaker'))
+ users = ranker(
+ ranking_list(contest, problems),
+ key=attrgetter('points', 'cumtime', 'tiebreaker'),
+ )
if show_current_virtual:
if participation is None and request.user.is_authenticated:
@@ -693,7 +958,10 @@ def get_contest_ranking_list(request, contest, participation=None, ranking_list=
if participation is None or participation.contest_id != contest.id:
participation = None
if participation is not None and participation.virtual:
- users = chain([('-', make_contest_ranking_profile(contest, participation, problems))], users)
+ users = chain(
+ [('-', make_contest_ranking_profile(contest, participation, problems))],
+ users,
+ )
return users, problems
@@ -706,12 +974,16 @@ def contest_ranking_ajax(request, contest, participation=None):
raise Http404()
users, problems = get_contest_ranking_list(request, contest, participation)
- return render(request, 'contest/ranking-table.html', {
- 'users': users,
- 'problems': problems,
- 'contest': contest,
- 'has_rating': contest.ratings.exists(),
- })
+ return render(
+ request,
+ 'contest/ranking-table.html',
+ {
+ 'users': users,
+ 'problems': problems,
+ 'contest': contest,
+ 'has_rating': contest.ratings.exists(),
+ },
+ )
class ContestRankingBase(ContestMixin, TitleMixin, DetailView):
@@ -749,9 +1021,12 @@ def get_title(self):
def get_ranking_list(self):
if not self.object.can_see_full_scoreboard(self.request.user):
- queryset = self.object.users.filter(user=self.request.profile, virtual=ContestParticipation.LIVE)
+ queryset = self.object.users.filter(
+ user=self.request.profile, virtual=ContestParticipation.LIVE
+ )
return get_contest_ranking_list(
- self.request, self.object,
+ self.request,
+ self.object,
ranking_list=partial(base_contest_ranking_list, queryset=queryset),
ranker=lambda users, key: ((_('???'), user) for user in users),
)
@@ -769,23 +1044,40 @@ class ContestParticipationList(LoginRequiredMixin, ContestRankingBase):
def get_title(self):
if self.profile == self.request.profile:
- return _('Your participation in %(contest)s') % {'contest': self.object.name}
+ return _('Your participation in %(contest)s') % {
+ 'contest': self.object.name
+ }
return _("%(user)s's participation in %(contest)s") % {
- 'user': self.profile.username, 'contest': self.object.name,
+ 'user': self.profile.username,
+ 'contest': self.object.name,
}
def get_ranking_list(self):
- if not self.object.can_see_full_scoreboard(self.request.user) and self.profile != self.request.profile:
+ if (
+ not self.object.can_see_full_scoreboard(self.request.user)
+ and self.profile != self.request.profile
+ ):
raise Http404()
- queryset = self.object.users.filter(user=self.profile, virtual__gte=0).order_by('-virtual')
- live_link = format_html('{0} ', _('Live'), self.profile.username,
- reverse('contest_ranking', args=[self.object.key]))
+ queryset = self.object.users.filter(user=self.profile, virtual__gte=0).order_by(
+ '-virtual'
+ )
+ live_link = format_html(
+ '{0} ',
+ _('Live'),
+ self.profile.username,
+ reverse('contest_ranking', args=[self.object.key]),
+ )
return get_contest_ranking_list(
- self.request, self.object, show_current_virtual=False,
+ self.request,
+ self.object,
+ show_current_virtual=False,
ranking_list=partial(base_contest_ranking_list, queryset=queryset),
- ranker=lambda users, key: ((user.participation.virtual or live_link, user) for user in users))
+ ranker=lambda users, key: (
+ (user.participation.virtual or live_link, user) for user in users
+ ),
+ )
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
@@ -826,7 +1118,9 @@ class ContestMossMixin(ContestMixin, PermissionRequiredMixin):
def get_object(self, queryset=None):
contest = super().get_object(queryset)
- if settings.MOSS_API_KEY is None or not contest.is_editable_by(self.request.user):
+ if settings.MOSS_API_KEY is None or not contest.is_editable_by(
+ self.request.user
+ ):
raise Http404()
return contest
@@ -840,8 +1134,14 @@ def get_title(self):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- problems = list(map(attrgetter('problem'), self.object.contest_problems.order_by('order')
- .select_related('problem')))
+ problems = list(
+ map(
+ attrgetter('problem'),
+ self.object.contest_problems.order_by('order').select_related(
+ 'problem'
+ ),
+ )
+ )
languages = list(map(itemgetter(0), ContestMoss.LANG_MAPPING))
results = ContestMoss.objects.filter(contest=self.object)
@@ -854,7 +1154,9 @@ def get_context_data(self, **kwargs):
context['languages'] = languages
context['has_results'] = results.exists()
- context['moss_results'] = [(problem, moss_results[problem]) for problem in problems]
+ context['moss_results'] = [
+ (problem, moss_results[problem]) for problem in problems
+ ]
return context
@@ -862,7 +1164,8 @@ def post(self, request, *args, **kwargs):
self.object = self.get_object()
status = run_moss.delay(self.object.key)
return redirect_to_task_status(
- status, message=_('Running MOSS for %s...') % (self.object.name,),
+ status,
+ message=_('Running MOSS for %s...') % (self.object.name,),
redirect=reverse('contest_moss', args=(self.object.key,)),
)
diff --git a/judge/views/error.py b/judge/views/error.py
index 113422d74e..11eca9d12f 100644
--- a/judge/views/error.py
+++ b/judge/views/error.py
@@ -10,24 +10,37 @@ def error(request, context, status):
def error404(request, exception=None):
# TODO: "panic: go back"
- return render(request, 'generic-message.html', {
- 'title': _('404 error'),
- 'message': _('Could not find page "%s"') % request.path,
- }, status=404)
+ return render(
+ request,
+ 'generic-message.html',
+ {
+ 'title': _('404 error'),
+ 'message': _('Could not find page "%s"') % request.path,
+ },
+ status=404,
+ )
def error403(request, exception=None):
- return error(request, {
- 'id': 'unauthorized_access',
- 'description': _('no permission for %s') % request.path,
- 'code': 403,
- }, 403)
+ return error(
+ request,
+ {
+ 'id': 'unauthorized_access',
+ 'description': _('no permission for %s') % request.path,
+ 'code': 403,
+ },
+ 403,
+ )
def error500(request):
- return error(request, {
- 'id': 'invalid_state',
- 'description': _('corrupt page %s') % request.path,
- 'traceback': traceback.format_exc(),
- 'code': 500,
- }, 500)
+ return error(
+ request,
+ {
+ 'id': 'invalid_state',
+ 'description': _('corrupt page %s') % request.path,
+ 'traceback': traceback.format_exc(),
+ 'code': 500,
+ },
+ 500,
+ )
diff --git a/judge/views/mailgun.py b/judge/views/mailgun.py
index 3bd8321125..cee9948efc 100644
--- a/judge/views/mailgun.py
+++ b/judge/views/mailgun.py
@@ -20,6 +20,7 @@
class MailgunActivationView(View):
if hasattr(settings, 'MAILGUN_ACCESS_KEY'):
+
def post(self, request, *args, **kwargs):
params = request.POST
timestamp = params.get('timestamp', '')
@@ -28,9 +29,20 @@ def post(self, request, *args, **kwargs):
logger.debug('Received request: %s', params)
- if signature != hmac.new(key=utf8bytes(settings.MAILGUN_ACCESS_KEY),
- msg=utf8bytes('%s%s' % (timestamp, token)), digestmod=hashlib.sha256).hexdigest():
- logger.info('Rejected request: signature: %s, timestamp: %s, token: %s', signature, timestamp, token)
+ if (
+ signature
+ != hmac.new(
+ key=utf8bytes(settings.MAILGUN_ACCESS_KEY),
+ msg=utf8bytes('%s%s' % (timestamp, token)),
+ digestmod=hashlib.sha256,
+ ).hexdigest()
+ ):
+ logger.info(
+ 'Rejected request: signature: %s, timestamp: %s, token: %s',
+ signature,
+ timestamp,
+ token,
+ )
raise PermissionDenied()
_, sender = parseaddr(params.get('from'))
if not sender:
@@ -39,25 +51,41 @@ def post(self, request, *args, **kwargs):
try:
user = User.objects.get(email__iexact=sender)
except (User.DoesNotExist, User.MultipleObjectsReturned):
- logger.info('Rejected unknown sender: %s: %s', sender, params.get('from'))
+ logger.info(
+ 'Rejected unknown sender: %s: %s', sender, params.get('from')
+ )
return HttpResponse(status=406)
try:
registration = RegistrationProfile.objects.get(user=user)
except RegistrationProfile.DoesNotExist:
- logger.info('Rejected sender without RegistrationProfile: %s: %s', sender, params.get('from'))
+ logger.info(
+ 'Rejected sender without RegistrationProfile: %s: %s',
+ sender,
+ params.get('from'),
+ )
return HttpResponse(status=406)
if registration.activated:
- logger.info('Rejected activated sender: %s: %s', sender, params.get('from'))
+ logger.info(
+ 'Rejected activated sender: %s: %s', sender, params.get('from')
+ )
return HttpResponse(status=406)
key = registration.activation_key
- if key in params.get('body-plain', '') or key in params.get('body-html', ''):
- if RegistrationProfile.objects.activate_user(key, get_current_site(request)):
+ if key in params.get('body-plain', '') or key in params.get(
+ 'body-html', ''
+ ):
+ if RegistrationProfile.objects.activate_user(
+ key, get_current_site(request)
+ ):
logger.info('Activated sender: %s: %s', sender, params.get('from'))
return HttpResponse('Activated', status=200)
- logger.info('Failed to activate sender: %s: %s', sender, params.get('from'))
+ logger.info(
+ 'Failed to activate sender: %s: %s', sender, params.get('from')
+ )
else:
- logger.info('Activation key not found: %s: %s', sender, params.get('from'))
+ logger.info(
+ 'Activation key not found: %s: %s', sender, params.get('from')
+ )
return HttpResponse(status=406)
@method_decorator(csrf_exempt)
diff --git a/judge/views/organization.py b/judge/views/organization.py
index dcf0a50746..91207c8bb5 100644
--- a/judge/views/organization.py
+++ b/judge/views/organization.py
@@ -16,23 +16,47 @@
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _, gettext_lazy, ngettext
from django.views.generic import DetailView, FormView, ListView, UpdateView, View
-from django.views.generic.detail import SingleObjectMixin, SingleObjectTemplateResponseMixin
+from django.views.generic.detail import (
+ SingleObjectMixin,
+ SingleObjectTemplateResponseMixin,
+)
from reversion import revisions
from judge.forms import EditOrganizationForm
from judge.models import Class, Organization, OrganizationRequest, Profile
from judge.utils.ranker import ranker
-from judge.utils.views import DiggPaginatorMixin, QueryStringSortMixin, TitleMixin, generic_message
-
-__all__ = ['OrganizationList', 'OrganizationHome', 'OrganizationUsers', 'OrganizationMembershipChange',
- 'JoinOrganization', 'LeaveOrganization', 'EditOrganization', 'RequestJoinOrganization',
- 'OrganizationRequestDetail', 'OrganizationRequestView', 'OrganizationRequestLog',
- 'KickUserWidgetView', 'ClassHome', 'RequestJoinClass']
+from judge.utils.views import (
+ DiggPaginatorMixin,
+ QueryStringSortMixin,
+ TitleMixin,
+ generic_message,
+)
+
+__all__ = [
+ 'OrganizationList',
+ 'OrganizationHome',
+ 'OrganizationUsers',
+ 'OrganizationMembershipChange',
+ 'JoinOrganization',
+ 'LeaveOrganization',
+ 'EditOrganization',
+ 'RequestJoinOrganization',
+ 'OrganizationRequestDetail',
+ 'OrganizationRequestView',
+ 'OrganizationRequestLog',
+ 'KickUserWidgetView',
+ 'ClassHome',
+ 'RequestJoinClass',
+]
def users_for_template(users, order):
- return ranker(users.filter(is_unlisted=False).order_by(order)
- .select_related('user').defer('about', 'user_script', 'notes'))
+ return ranker(
+ users.filter(is_unlisted=False)
+ .order_by(order)
+ .select_related('user')
+ .defer('about', 'user_script', 'notes')
+ )
class OrganizationMixin(object):
@@ -50,11 +74,17 @@ def dispatch(self, request, *args, **kwargs):
except Http404:
key = kwargs.get(self.slug_url_kwarg, None)
if key:
- return generic_message(request, _('No such organization'),
- _('Could not find an organization with the key "%s".') % key)
+ return generic_message(
+ request,
+ _('No such organization'),
+ _('Could not find an organization with the key "%s".') % key,
+ )
else:
- return generic_message(request, _('No such organization'),
- _('Could not find such organization.'))
+ return generic_message(
+ request,
+ _('No such organization'),
+ _('Could not find such organization.'),
+ )
def can_edit_organization(self, org=None):
if org is None:
@@ -85,8 +115,12 @@ class OrganizationDetailView(OrganizationMixin, DetailView):
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.slug != kwargs['slug']:
- return HttpResponsePermanentRedirect(reverse(
- request.resolver_match.url_name, args=(self.object.id, self.object.slug)))
+ return HttpResponsePermanentRedirect(
+ reverse(
+ request.resolver_match.url_name,
+ args=(self.object.id, self.object.slug),
+ )
+ )
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
@@ -98,7 +132,12 @@ class OrganizationList(TitleMixin, ListView):
title = gettext_lazy('Organizations')
def get_queryset(self):
- return super(OrganizationList, self).get_queryset().annotate(member_count=Count('member')).order_by('name')
+ return (
+ super(OrganizationList, self)
+ .get_queryset()
+ .annotate(member_count=Count('member'))
+ .order_by('name')
+ )
class OrganizationHome(OrganizationDetailView):
@@ -108,23 +147,31 @@ def get_context_data(self, **kwargs):
context = super(OrganizationHome, self).get_context_data(**kwargs)
context['title'] = self.object.name
context['can_edit'] = self.can_edit_organization()
- context['can_review_requests'] = not self.object.is_open and self.request.user.is_authenticated and (
- self.object.can_review_all_requests(self.request.profile) or
- self.object.can_review_class_requests(self.request.profile)
+ context['can_review_requests'] = (
+ not self.object.is_open
+ and self.request.user.is_authenticated
+ and (
+ self.object.can_review_all_requests(self.request.profile)
+ or self.object.can_review_class_requests(self.request.profile)
+ )
)
classes = self.object.classes.filter(is_active=True)
if self.request.user.is_authenticated:
- classes = classes.annotate(joined=Subquery(
- self.request.profile.classes.filter(id=OuterRef('id')).values('id'),
- )).order_by('-joined', 'name')
+ classes = classes.annotate(
+ joined=Subquery(
+ self.request.profile.classes.filter(id=OuterRef('id')).values('id'),
+ )
+ ).order_by('-joined', 'name')
else:
classes = classes.annotate(joined=Value(0, output_field=IntegerField()))
context['classes'] = classes
return context
-class OrganizationUsers(QueryStringSortMixin, DiggPaginatorMixin, BaseOrganizationListView):
+class OrganizationUsers(
+ QueryStringSortMixin, DiggPaginatorMixin, BaseOrganizationListView
+):
template_name = 'organization/users.html'
all_sorts = frozenset(('problem_count', 'rating', 'performance_points'))
default_desc = all_sorts
@@ -133,8 +180,12 @@ class OrganizationUsers(QueryStringSortMixin, DiggPaginatorMixin, BaseOrganizati
context_object_name = 'users'
def get_queryset(self):
- return self.object.members.filter(is_unlisted=False).order_by(self.order).select_related('user') \
+ return (
+ self.object.members.filter(is_unlisted=False)
+ .order_by(self.order)
+ .select_related('user')
.defer('about', 'user_script', 'notes')
+ )
def get_context_data(self, **kwargs):
context = super(OrganizationUsers, self).get_context_data(**kwargs)
@@ -142,14 +193,18 @@ def get_context_data(self, **kwargs):
context['users'] = ranker(context['users'])
context['partial'] = True
context['is_admin'] = self.can_edit_organization()
- context['kick_url'] = reverse('organization_user_kick', args=[self.object.id, self.object.slug])
+ context['kick_url'] = reverse(
+ 'organization_user_kick', args=[self.object.id, self.object.slug]
+ )
context['first_page_href'] = '.'
context.update(self.get_sort_context())
context.update(self.get_sort_paginate_context())
return context
-class OrganizationMembershipChange(LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View):
+class OrganizationMembershipChange(
+ LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View
+):
def post(self, request, *args, **kwargs):
org = self.get_object()
response = self.handle(request, org, request.profile)
@@ -164,18 +219,27 @@ def handle(self, request, org, profile):
class JoinOrganization(OrganizationMembershipChange):
def handle(self, request, org, profile):
if profile.organizations.filter(id=org.id).exists():
- return generic_message(request, _('Joining organization'), _('You are already in the organization.'))
+ return generic_message(
+ request,
+ _('Joining organization'),
+ _('You are already in the organization.'),
+ )
if not org.is_open:
- return generic_message(request, _('Joining organization'), _('This organization is not open.'))
+ return generic_message(
+ request, _('Joining organization'), _('This organization is not open.')
+ )
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
if profile.organizations.filter(is_open=True).count() >= max_orgs:
return generic_message(
- request, _('Joining organization'),
- ngettext('You may not be part of more than {count} public organization.',
- 'You may not be part of more than {count} public organizations.',
- max_orgs).format(count=max_orgs),
+ request,
+ _('Joining organization'),
+ ngettext(
+ 'You may not be part of more than {count} public organization.',
+ 'You may not be part of more than {count} public organizations.',
+ max_orgs,
+ ).format(count=max_orgs),
)
profile.organizations.add(org)
@@ -186,7 +250,11 @@ def handle(self, request, org, profile):
class LeaveOrganization(OrganizationMembershipChange):
def handle(self, request, org, profile):
if not profile.organizations.filter(id=org.id).exists():
- return generic_message(request, _('Leaving organization'), _('You are not in "%s".') % org.short_name)
+ return generic_message(
+ request,
+ _('Leaving organization'),
+ _('You are not in "%s".') % org.short_name,
+ )
profile.organizations.remove(org)
cache.delete(make_template_fragment_key('org_member_count', (org.id,)))
@@ -213,8 +281,11 @@ class RequestJoinOrganization(LoginRequiredMixin, SingleObjectMixin, FormView):
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.requests.filter(user=self.request.profile, state='P').exists():
- return generic_message(self.request, _("Can't request to join %s") % self.object.name,
- _('You already have a pending request to join %s.') % self.object.name)
+ return generic_message(
+ self.request,
+ _("Can't request to join %s") % self.object.name,
+ _('You already have a pending request to join %s.') % self.object.name,
+ )
if self.object.is_open:
raise Http404()
return super(RequestJoinOrganization, self).dispatch(request, *args, **kwargs)
@@ -238,9 +309,16 @@ def form_valid(self, form):
request.request_class = form.cleaned_data['class_']
request.state = 'P'
request.save()
- return HttpResponseRedirect(reverse('request_organization_detail', args=(
- request.organization.id, request.organization.slug, request.id,
- )))
+ return HttpResponseRedirect(
+ reverse(
+ 'request_organization_detail',
+ args=(
+ request.organization.id,
+ request.organization.slug,
+ request.id,
+ ),
+ )
+ )
class OrganizationRequestDetail(LoginRequiredMixin, TitleMixin, DetailView):
@@ -252,16 +330,26 @@ class OrganizationRequestDetail(LoginRequiredMixin, TitleMixin, DetailView):
def get_object(self, queryset=None):
object = super(OrganizationRequestDetail, self).get_object(queryset)
profile = self.request.profile
- if object.user_id != profile.id and not object.organization.admins.filter(id=profile.id).exists() and (
- not object.request_class or not object.request_class.admins.filter(id=profile.id).exists()):
+ if (
+ object.user_id != profile.id
+ and not object.organization.admins.filter(id=profile.id).exists()
+ and (
+ not object.request_class
+ or not object.request_class.admins.filter(id=profile.id).exists()
+ )
+ ):
raise PermissionDenied()
return object
-OrganizationRequestFormSet = modelformset_factory(OrganizationRequest, extra=0, fields=('state',), can_delete=True)
+OrganizationRequestFormSet = modelformset_factory(
+ OrganizationRequest, extra=0, fields=('state',), can_delete=True
+)
-class OrganizationRequestBaseView(LoginRequiredMixin, SingleObjectTemplateResponseMixin, SingleObjectMixin, View):
+class OrganizationRequestBaseView(
+ LoginRequiredMixin, SingleObjectTemplateResponseMixin, SingleObjectMixin, View
+):
model = Organization
slug_field = 'key'
slug_url_kwarg = 'key'
@@ -279,10 +367,16 @@ def get_object(self, queryset=None):
def get_requests(self):
queryset = self.object.requests.select_related('user__user').defer(
- 'user__about', 'user__notes', 'user__user_script',
+ 'user__about',
+ 'user__notes',
+ 'user__user_script',
)
if not self.edit_all:
- queryset = queryset.filter(request_class__in=self.object.classes.filter(admins__id=self.request.profile.id))
+ queryset = queryset.filter(
+ request_class__in=self.object.classes.filter(
+ admins__id=self.request.profile.id
+ )
+ )
return queryset
def get_context_data(self, **kwargs):
@@ -312,19 +406,39 @@ def get_requests(self):
def post(self, request, *args, **kwargs):
self.object = organization = self.get_object()
- self.formset = formset = OrganizationRequestFormSet(request.POST, request.FILES, queryset=self.get_requests())
+ self.formset = formset = OrganizationRequestFormSet(
+ request.POST, request.FILES, queryset=self.get_requests()
+ )
if formset.is_valid():
if organization.slots is not None:
deleted_set = set(formset.deleted_forms)
- to_approve = sum(form.cleaned_data['state'] == 'A' for form in formset.forms if form not in deleted_set)
+ to_approve = sum(
+ form.cleaned_data['state'] == 'A'
+ for form in formset.forms
+ if form not in deleted_set
+ )
can_add = organization.slots - organization.members.count()
if to_approve > can_add:
- msg1 = ngettext('Your organization can only receive %d more member.',
- 'Your organization can only receive %d more members.', can_add) % can_add
- msg2 = ngettext('You cannot approve %d user.',
- 'You cannot approve %d users.', to_approve) % to_approve
+ msg1 = (
+ ngettext(
+ 'Your organization can only receive %d more member.',
+ 'Your organization can only receive %d more members.',
+ can_add,
+ )
+ % can_add
+ )
+ msg2 = (
+ ngettext(
+ 'You cannot approve %d user.',
+ 'You cannot approve %d users.',
+ to_approve,
+ )
+ % to_approve
+ )
messages.error(request, msg1 + '\n' + msg2)
- return self.render_to_response(self.get_context_data(object=organization))
+ return self.render_to_response(
+ self.get_context_data(object=organization)
+ )
approved, rejected = 0, 0
for obj in formset.save():
@@ -335,10 +449,16 @@ def post(self, request, *args, **kwargs):
approved += 1
elif obj.state == 'R':
rejected += 1
- messages.success(request,
- ngettext('Approved %d user.', 'Approved %d users.', approved) % approved + '\n' +
- ngettext('Rejected %d user.', 'Rejected %d users.', rejected) % rejected)
- cache.delete(make_template_fragment_key('org_member_count', (organization.id,)))
+ messages.success(
+ request,
+ ngettext('Approved %d user.', 'Approved %d users.', approved) % approved
+ + '\n'
+ + ngettext('Rejected %d user.', 'Rejected %d users.', rejected)
+ % rejected,
+ )
+ cache.delete(
+ make_template_fragment_key('org_member_count', (organization.id,))
+ )
return HttpResponseRedirect(request.get_full_path())
return self.render_to_response(self.get_context_data(object=organization))
@@ -377,8 +497,9 @@ def get_object(self, queryset=None):
def get_form(self, form_class=None):
form = super(EditOrganization, self).get_form(form_class)
- form.fields['admins'].queryset = \
- Profile.objects.filter(Q(organizations=self.object) | Q(admin_of=self.object)).distinct()
+ form.fields['admins'].queryset = Profile.objects.filter(
+ Q(organizations=self.object) | Q(admin_of=self.object)
+ ).distinct()
return form
def form_valid(self, form):
@@ -391,27 +512,45 @@ def dispatch(self, request, *args, **kwargs):
try:
return super(EditOrganization, self).dispatch(request, *args, **kwargs)
except PermissionDenied:
- return generic_message(request, _("Can't edit organization"),
- _('You are not allowed to edit this organization.'), status=403)
+ return generic_message(
+ request,
+ _("Can't edit organization"),
+ _('You are not allowed to edit this organization.'),
+ status=403,
+ )
-class KickUserWidgetView(LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View):
+class KickUserWidgetView(
+ LoginRequiredMixin, OrganizationMixin, SingleObjectMixin, View
+):
def post(self, request, *args, **kwargs):
organization = self.get_object()
if not self.can_edit_organization(organization):
- return generic_message(request, _("Can't edit organization"),
- _('You are not allowed to kick people from this organization.'), status=403)
+ return generic_message(
+ request,
+ _("Can't edit organization"),
+ _('You are not allowed to kick people from this organization.'),
+ status=403,
+ )
try:
user = Profile.objects.get(id=request.POST.get('user', None))
except Profile.DoesNotExist:
- return generic_message(request, _("Can't kick user"),
- _('The user you are trying to kick does not exist!'), status=400)
+ return generic_message(
+ request,
+ _("Can't kick user"),
+ _('The user you are trying to kick does not exist!'),
+ status=400,
+ )
if not organization.members.filter(id=user.id).exists():
- return generic_message(request, _("Can't kick user"),
- _('The user you are trying to kick is not in organization: %s') %
- organization.name, status=400)
+ return generic_message(
+ request,
+ _("Can't kick user"),
+ _('The user you are trying to kick is not in organization: %s')
+ % organization.name,
+ status=400,
+ )
organization.members.remove(user)
return HttpResponseRedirect(organization.get_users_url())
@@ -425,7 +564,11 @@ class ClassMixin(TitleMixin, SingleObjectTemplateResponseMixin, SingleObjectMixi
def get(self, request, *args, **kwargs):
self.object = self.get_object()
org = self.object.organization
- if self.object.slug != kwargs['cslug'] or org.id != kwargs['pk'] or org.slug != kwargs['slug']:
+ if (
+ self.object.slug != kwargs['cslug']
+ or org.id != kwargs['pk']
+ or org.slug != kwargs['slug']
+ ):
return HttpResponsePermanentRedirect(self.object.get_absolute_url())
context = self.get_context_data()
return self.render_to_response(context)
@@ -447,14 +590,19 @@ def get_context_data(self, **kwargs):
def get_content_title(self):
org = self.object.organization
- return mark_safe(escape(_('Class {name} in {organization}')).format(
- name=escape(self.object.name),
- organization=format_html('{1} ', org.get_absolute_url(), org.name),
- ))
+ return mark_safe(
+ escape(_('Class {name} in {organization}')).format(
+ name=escape(self.object.name),
+ organization=format_html(
+ '{1} ', org.get_absolute_url(), org.name
+ ),
+ )
+ )
def get_title(self):
return _('Class {name} - {organization}').format(
- name=self.object.name, organization=self.object.organization.name,
+ name=self.object.name,
+ organization=self.object.organization.name,
)
@@ -473,16 +621,24 @@ def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
org = self.object.organization
if not org.members.filter(id=self.request.profile.id).exists():
- return HttpResponseRedirect(reverse('request_organization', args=(org.id, org.slug)))
- if org.requests.filter(user=self.request.profile, state='P', request_class=self.object).exists():
- return generic_message(self.request, _("Can't request to join %s") % self.object.name,
- _('You already have a pending request to join %s.') % self.object.name)
+ return HttpResponseRedirect(
+ reverse('request_organization', args=(org.id, org.slug))
+ )
+ if org.requests.filter(
+ user=self.request.profile, state='P', request_class=self.object
+ ).exists():
+ return generic_message(
+ self.request,
+ _("Can't request to join %s") % self.object.name,
+ _('You already have a pending request to join %s.') % self.object.name,
+ )
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = _('Request to join {name} in {organization}').format(
- name=self.object.name, organization=self.object.organization.name,
+ name=self.object.name,
+ organization=self.object.organization.name,
)
return context
@@ -494,6 +650,13 @@ def form_valid(self, form):
request.request_class = self.object
request.state = 'P'
request.save()
- return HttpResponseRedirect(reverse('request_organization_detail', args=(
- request.organization.id, request.organization.slug, request.id,
- )))
+ return HttpResponseRedirect(
+ reverse(
+ 'request_organization_detail',
+ args=(
+ request.organization.id,
+ request.organization.slug,
+ request.id,
+ ),
+ )
+ )
diff --git a/judge/views/preview.py b/judge/views/preview.py
index 2b3d2046b0..3e2f99680b 100644
--- a/judge/views/preview.py
+++ b/judge/views/preview.py
@@ -9,9 +9,11 @@ def post(self, request, *args, **kwargs):
except KeyError:
return HttpResponseBadRequest('No preview data specified.')
- return self.render_to_response(self.get_context_data(
- preview_data=data,
- ))
+ return self.render_to_response(
+ self.get_context_data(
+ preview_data=data,
+ )
+ )
class ProblemMarkdownPreviewView(MarkdownPreviewView):
diff --git a/judge/views/problem.py b/judge/views/problem.py
index 3fb2365f47..088615f112 100644
--- a/judge/views/problem.py
+++ b/judge/views/problem.py
@@ -10,10 +10,26 @@
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db import transaction
-from django.db.models import BooleanField, Case, CharField, Count, F, FilteredRelation, Prefetch, Q, When
+from django.db.models import (
+ BooleanField,
+ Case,
+ CharField,
+ Count,
+ F,
+ FilteredRelation,
+ Prefetch,
+ Q,
+ When,
+)
from django.db.models.functions import Coalesce
from django.db.utils import ProgrammingError
-from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect, JsonResponse
+from django.http import (
+ Http404,
+ HttpResponse,
+ HttpResponseForbidden,
+ HttpResponseRedirect,
+ JsonResponse,
+)
from django.shortcuts import get_object_or_404
from django.template.loader import get_template
from django.urls import reverse
@@ -28,19 +44,44 @@
from judge.comments import CommentedDetailView
from judge.forms import ProblemCloneForm, ProblemPointsVoteForm, ProblemSubmitForm
-from judge.models import ContestSubmission, Judge, Language, Problem, ProblemGroup, ProblemPointsVote, \
- ProblemTranslation, ProblemType, RuntimeVersion, Solution, Submission, SubmissionSource
+from judge.models import (
+ ContestSubmission,
+ Judge,
+ Language,
+ Problem,
+ ProblemGroup,
+ ProblemPointsVote,
+ ProblemTranslation,
+ ProblemType,
+ RuntimeVersion,
+ Solution,
+ Submission,
+ SubmissionSource,
+)
from judge.utils.diggpaginator import DiggPaginator
from judge.utils.opengraph import generate_opengraph
from judge.utils.pdfoid import PDF_RENDERING_ENABLED, render_pdf
-from judge.utils.problems import contest_attempted_ids, contest_completed_ids, hot_problems, user_attempted_ids, \
- user_completed_ids
+from judge.utils.problems import (
+ contest_attempted_ids,
+ contest_completed_ids,
+ hot_problems,
+ user_attempted_ids,
+ user_completed_ids,
+)
from judge.utils.strings import safe_float_or_none, safe_int_or_none
from judge.utils.tickets import own_ticket_filter
-from judge.utils.views import QueryStringSortMixin, SingleObjectFormView, TitleMixin, add_file_response, generic_message
+from judge.utils.views import (
+ QueryStringSortMixin,
+ SingleObjectFormView,
+ TitleMixin,
+ add_file_response,
+ generic_message,
+)
-recjk = re.compile(r'[\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u3005\u3007\u3021-\u3029\u3038-\u303A\u303B\u3400-\u4DB5'
- r'\u4E00-\u9FC3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\U00020000-\U0002A6D6\U0002F800-\U0002FA1D]')
+recjk = re.compile(
+ r'[\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u3005\u3007\u3021-\u3029\u3038-\u303A\u303B\u3400-\u4DB5'
+ r'\u4E00-\u9FC3\uF900-\uFA2D\uFA30-\uFA6A\uFA70-\uFAD9\U00020000-\U0002A6D6\U0002F800-\U0002FA1D]'
+)
def get_contest_problem(problem, profile):
@@ -51,8 +92,11 @@ def get_contest_problem(problem, profile):
def get_contest_submission_count(problem, profile, virtual):
- return profile.current_contest.submissions.exclude(submission__status__in=['IE']) \
- .filter(problem__problem=problem, participation__virtual=virtual).count()
+ return (
+ profile.current_contest.submissions.exclude(submission__status__in=['IE'])
+ .filter(problem__problem=problem, participation__virtual=virtual)
+ .count()
+ )
class ProblemMixin(object):
@@ -68,8 +112,12 @@ def get_object(self, queryset=None):
def no_such_problem(self):
code = self.kwargs.get(self.slug_url_kwarg, None)
- return generic_message(self.request, _('No such problem'),
- _('Could not find a problem with the code "%s".') % code, status=404)
+ return generic_message(
+ self.request,
+ _('No such problem'),
+ _('Could not find a problem with the code "%s".') % code,
+ status=404,
+ )
def get(self, request, *args, **kwargs):
try:
@@ -106,7 +154,9 @@ def profile(self):
return self.request.profile
-class ProblemSolution(SolvedProblemMixin, ProblemMixin, TitleMixin, CommentedDetailView):
+class ProblemSolution(
+ SolvedProblemMixin, ProblemMixin, TitleMixin, CommentedDetailView
+):
context_object_name = 'problem'
template_name = 'problem/editorial.html'
@@ -114,9 +164,15 @@ def get_title(self):
return _('Editorial for {0}').format(self.object.name)
def get_content_title(self):
- return mark_safe(escape(_('Editorial for {0}')).format(
- format_html('{0} ', self.object.name, reverse('problem_detail', args=[self.object.code])),
- ))
+ return mark_safe(
+ escape(_('Editorial for {0}')).format(
+ format_html(
+ '{0} ',
+ self.object.name,
+ reverse('problem_detail', args=[self.object.code]),
+ ),
+ )
+ )
def get_context_data(self, **kwargs):
context = super(ProblemSolution, self).get_context_data(**kwargs)
@@ -134,8 +190,12 @@ def get_comment_page(self):
def no_such_problem(self):
code = self.kwargs.get(self.slug_url_kwarg, None)
- return generic_message(self.request, _('No such editorial'),
- _('Could not find an editorial with the code "%s".') % code, status=404)
+ return generic_message(
+ self.request,
+ _('No such editorial'),
+ _('Could not find an editorial with the code "%s".') % code,
+ status=404,
+ )
class ProblemDetail(ProblemMixin, SolvedProblemMixin, CommentedDetailView):
@@ -149,10 +209,17 @@ def get_context_data(self, **kwargs):
context = super(ProblemDetail, self).get_context_data(**kwargs)
user = self.request.user
authed = user.is_authenticated
- context['has_submissions'] = authed and Submission.objects.filter(user=user.profile,
- problem=self.object).exists()
- contest_problem = (None if not authed or user.profile.current_contest is None else
- get_contest_problem(self.object, user.profile))
+ context['has_submissions'] = (
+ authed
+ and Submission.objects.filter(
+ user=user.profile, problem=self.object
+ ).exists()
+ )
+ contest_problem = (
+ None
+ if not authed or user.profile.current_contest is None
+ else get_contest_problem(self.object, user.profile)
+ )
context['contest_problem'] = contest_problem
if contest_problem:
clarifications = self.object.clarifications
@@ -160,12 +227,20 @@ def get_context_data(self, **kwargs):
context['clarifications'] = clarifications.order_by('-date')
context['submission_limit'] = contest_problem.max_submissions
if contest_problem.max_submissions:
- context['submissions_left'] = max(contest_problem.max_submissions -
- get_contest_submission_count(self.object, user.profile,
- user.profile.current_contest.virtual), 0)
+ context['submissions_left'] = max(
+ contest_problem.max_submissions
+ - get_contest_submission_count(
+ self.object, user.profile, user.profile.current_contest.virtual
+ ),
+ 0,
+ )
- context['available_judges'] = Judge.objects.filter(online=True, problems=self.object)
- context['show_languages'] = self.object.allowed_languages.count() != Language.objects.count()
+ context['available_judges'] = Judge.objects.filter(
+ online=True, problems=self.object
+ )
+ context['show_languages'] = (
+ self.object.allowed_languages.count() != Language.objects.count()
+ )
context['has_pdf_render'] = PDF_RENDERING_ENABLED
context['completed_problem_ids'] = self.get_completed_problems()
context['attempted_problems'] = self.get_attempted_problems()
@@ -177,14 +252,18 @@ def get_context_data(self, **kwargs):
if not can_edit:
tickets = tickets.filter(own_ticket_filter(user.profile.id))
context['has_tickets'] = tickets.exists()
- context['num_open_tickets'] = tickets.filter(is_open=True).values('id').distinct().count()
+ context['num_open_tickets'] = (
+ tickets.filter(is_open=True).values('id').distinct().count()
+ )
try:
context['editorial'] = Solution.objects.get(problem=self.object)
except ObjectDoesNotExist:
pass
try:
- translation = self.object.translations.get(language=self.request.LANGUAGE_CODE)
+ translation = self.object.translations.get(
+ language=self.request.LANGUAGE_CODE
+ )
except ProblemTranslation.DoesNotExist:
context['title'] = self.object.name
context['language'] = settings.LANGUAGE_CODE
@@ -197,15 +276,20 @@ def get_context_data(self, **kwargs):
context['translated'] = True
if not self.object.og_image or not self.object.summary:
- metadata = generate_opengraph('generated-meta-problem:%s:%d' % (context['language'], self.object.id),
- context['description'], 'problem')
+ metadata = generate_opengraph(
+ 'generated-meta-problem:%s:%d' % (context['language'], self.object.id),
+ context['description'],
+ 'problem',
+ )
context['meta_description'] = self.object.summary or metadata[0]
context['og_image'] = self.object.og_image or metadata[1]
context['vote_perm'] = self.object.vote_permission_for_user(user)
if context['vote_perm'].can_vote():
try:
- context['vote'] = ProblemPointsVote.objects.get(voter=user.profile, problem=self.object)
+ context['vote'] = ProblemPointsVote.objects.get(
+ voter=user.profile, problem=self.object
+ )
except ObjectDoesNotExist:
context['vote'] = None
else:
@@ -225,7 +309,9 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
- context['vote'] = ProblemPointsVote.objects.get(voter=self.request.profile, problem=self.object)
+ context['vote'] = ProblemPointsVote.objects.get(
+ voter=self.request.profile, problem=self.object
+ )
except ObjectDoesNotExist:
context['vote'] = None
@@ -236,7 +322,9 @@ def get_context_data(self, **kwargs):
def post(self, request, *args, **kwargs):
problem = self.get_object()
if not problem.vote_permission_for_user(request.user).can_vote():
- return JsonResponse({'message': _('Not allowed to vote on this problem.')}, status=403)
+ return JsonResponse(
+ {'message': _('Not allowed to vote on this problem.')}, status=403
+ )
form = ProblemPointsVoteForm(request.POST)
if not form.is_valid():
@@ -244,7 +332,9 @@ def post(self, request, *args, **kwargs):
with transaction.atomic():
# Delete any pre-existing votes.
- ProblemPointsVote.objects.filter(voter=request.profile, problem=problem).delete()
+ ProblemPointsVote.objects.filter(
+ voter=request.profile, problem=problem
+ ).delete()
vote = form.save(commit=False)
vote.voter = request.profile
vote.problem = problem
@@ -254,14 +344,22 @@ def post(self, request, *args, **kwargs):
class DeleteProblemVote(ProblemMixin, SingleObjectMixin, View):
- http_method_names = ['options', 'post'] # This disables GET requests, even though ProblemMixin.get exists.
+ http_method_names = [
+ 'options',
+ 'post',
+ ] # This disables GET requests, even though ProblemMixin.get exists.
def post(self, request, *args, **kwargs):
problem = self.get_object()
if not problem.vote_permission_for_user(request.user).can_vote():
- return JsonResponse({'message': _('Not allowed to delete votes on this problem.')}, status=403)
+ return JsonResponse(
+ {'message': _('Not allowed to delete votes on this problem.')},
+ status=403,
+ )
- ProblemPointsVote.objects.filter(voter=request.profile, problem=problem).delete()
+ ProblemPointsVote.objects.filter(
+ voter=request.profile, problem=problem
+ ).delete()
return JsonResponse({'message': _('success')})
@@ -275,7 +373,11 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- votes = list(self.object.problem_points_votes.order_by('points').values_list('points', flat=True))
+ votes = list(
+ self.object.problem_points_votes.order_by('points').values_list(
+ 'points', flat=True
+ )
+ )
context['votes'] = votes
if votes:
@@ -317,12 +419,19 @@ def render_problem_pdf():
problem_name = trans.name if trans else problem.name
return render_pdf(
- html=get_template('problem/raw.html').render({
- 'problem': problem,
- 'problem_name': problem_name,
- 'description': trans.description if trans else problem.description,
- 'url': request.build_absolute_uri(),
- }).replace('"//', '"https://').replace("'//", "'https://"),
+ html=get_template('problem/raw.html')
+ .render(
+ {
+ 'problem': problem,
+ 'problem_name': problem_name,
+ 'description': trans.description
+ if trans
+ else problem.description,
+ 'url': request.build_absolute_uri(),
+ }
+ )
+ .replace('"//', '"https://')
+ .replace("'//", "'https://"),
title=problem_name,
)
@@ -360,22 +469,34 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
default_desc = frozenset(('points', 'ac_rate', 'user_count'))
default_sort = 'code'
- def get_paginator(self, queryset, per_page, orphans=0,
- allow_empty_first_page=True, **kwargs):
- paginator = DiggPaginator(queryset, per_page, body=6, padding=2, orphans=orphans,
- count=queryset.values('pk').count() if not self.in_contest else None,
- allow_empty_first_page=allow_empty_first_page, **kwargs)
+ def get_paginator(
+ self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
+ ):
+ paginator = DiggPaginator(
+ queryset,
+ per_page,
+ body=6,
+ padding=2,
+ orphans=orphans,
+ count=queryset.values('pk').count() if not self.in_contest else None,
+ allow_empty_first_page=allow_empty_first_page,
+ **kwargs,
+ )
if not self.in_contest:
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)
sort_key = self.order.lstrip('-')
if sort_key in self.sql_sort:
queryset = queryset.order_by(self.order, 'id')
elif sort_key == 'name':
- queryset = queryset.order_by(self.order.replace('name', 'i18n_name'), 'id')
+ queryset = queryset.order_by(
+ self.order.replace('name', 'i18n_name'), 'id'
+ )
elif sort_key == 'group':
queryset = queryset.order_by(self.order + '__name', 'id')
elif sort_key == 'editorial':
- queryset = queryset.order_by(self.order.replace('editorial', 'has_public_editorial'), 'id')
+ queryset = queryset.order_by(
+ self.order.replace('editorial', 'has_public_editorial'), 'id'
+ )
elif sort_key == 'solved':
if self.request.user.is_authenticated:
profile = self.request.profile
@@ -390,12 +511,18 @@ def _solved_sort_order(problem):
return -1
queryset = list(queryset)
- queryset.sort(key=_solved_sort_order, reverse=self.order.startswith('-'))
+ queryset.sort(
+ key=_solved_sort_order, reverse=self.order.startswith('-')
+ )
elif sort_key == 'type':
if self.show_types:
queryset = list(queryset)
- queryset.sort(key=lambda problem: problem.types_list[0] if problem.types_list else '',
- reverse=self.order.startswith('-'))
+ queryset.sort(
+ key=lambda problem: problem.types_list[0]
+ if problem.types_list
+ else '',
+ reverse=self.order.startswith('-'),
+ )
paginator.object_list = queryset
return paginator
@@ -406,33 +533,63 @@ def profile(self):
return self.request.profile
def get_contest_queryset(self):
- queryset = self.profile.current_contest.contest.contest_problems.select_related('problem__group') \
- .defer('problem__description').order_by('problem__code') \
- .annotate(user_count=Count('submission__participation', distinct=True)) \
- .annotate(i18n_translation=FilteredRelation(
- 'problem__translations', condition=Q(problem__translations__language=self.request.LANGUAGE_CODE),
- )).annotate(i18n_name=Coalesce(
- F('i18n_translation__name'), F('problem__name'), output_field=CharField(),
- )).order_by('order')
- return [{
- 'id': p['problem_id'],
- 'code': p['problem__code'],
- 'name': p['problem__name'],
- 'i18n_name': p['i18n_name'],
- 'group': {'full_name': p['problem__group__full_name']},
- 'points': p['points'],
- 'partial': p['partial'],
- 'user_count': p['user_count'],
- } for p in queryset.values('problem_id', 'problem__code', 'problem__name', 'i18n_name',
- 'problem__group__full_name', 'points', 'partial', 'user_count')]
+ queryset = (
+ self.profile.current_contest.contest.contest_problems.select_related(
+ 'problem__group'
+ )
+ .defer('problem__description')
+ .order_by('problem__code')
+ .annotate(user_count=Count('submission__participation', distinct=True))
+ .annotate(
+ i18n_translation=FilteredRelation(
+ 'problem__translations',
+ condition=Q(
+ problem__translations__language=self.request.LANGUAGE_CODE
+ ),
+ )
+ )
+ .annotate(
+ i18n_name=Coalesce(
+ F('i18n_translation__name'),
+ F('problem__name'),
+ output_field=CharField(),
+ )
+ )
+ .order_by('order')
+ )
+ return [
+ {
+ 'id': p['problem_id'],
+ 'code': p['problem__code'],
+ 'name': p['problem__name'],
+ 'i18n_name': p['i18n_name'],
+ 'group': {'full_name': p['problem__group__full_name']},
+ 'points': p['points'],
+ 'partial': p['partial'],
+ 'user_count': p['user_count'],
+ }
+ for p in queryset.values(
+ 'problem_id',
+ 'problem__code',
+ 'problem__name',
+ 'i18n_name',
+ 'problem__group__full_name',
+ 'points',
+ 'partial',
+ 'user_count',
+ )
+ ]
@staticmethod
def apply_full_text(queryset, query):
if recjk.search(query):
# MariaDB can't tokenize CJK properly, fallback to LIKE '%term%' for each term.
for term in query.split():
- queryset = queryset.filter(Q(code__icontains=term) | Q(name__icontains=term) |
- Q(description__icontains=term))
+ queryset = queryset.filter(
+ Q(code__icontains=term)
+ | Q(name__icontains=term)
+ | Q(description__icontains=term)
+ )
return queryset
return queryset.search(query, queryset.BOOLEAN).extra(order_by=['-relevance'])
@@ -445,18 +602,30 @@ def get_normal_queryset(self):
filter &= org_filter
if self.profile is not None:
filter = Problem.q_add_author_curator_tester(filter, self.profile)
- queryset = Problem.objects.filter(filter).select_related('group').defer('description', 'summary')
+ queryset = (
+ Problem.objects.filter(filter)
+ .select_related('group')
+ .defer('description', 'summary')
+ )
if self.profile is not None and self.hide_solved:
- queryset = queryset.exclude(id__in=Submission.objects
- .filter(user=self.profile, result='AC', case_points__gte=F('case_total'))
- .values_list('problem_id', flat=True))
+ queryset = queryset.exclude(
+ id__in=Submission.objects.filter(
+ user=self.profile, result='AC', case_points__gte=F('case_total')
+ ).values_list('problem_id', flat=True)
+ )
if self.show_types:
queryset = queryset.prefetch_related('types')
- queryset = queryset.annotate(has_public_editorial=Case(
- When(solution__is_public=True, solution__publish_on__lte=timezone.now(), then=True),
- default=False,
- output_field=BooleanField(),
- ))
+ queryset = queryset.annotate(
+ has_public_editorial=Case(
+ When(
+ solution__is_public=True,
+ solution__publish_on__lte=timezone.now(),
+ then=True,
+ ),
+ default=False,
+ output_field=BooleanField(),
+ )
+ )
if self.has_public_editorial:
queryset = queryset.filter(has_public_editorial=True)
if self.category is not None:
@@ -464,14 +633,21 @@ def get_normal_queryset(self):
if self.selected_types:
queryset = queryset.filter(types__in=self.selected_types)
if 'search' in self.request.GET:
- self.search_query = query = ' '.join(self.request.GET.getlist('search')).strip()
+ self.search_query = query = ' '.join(
+ self.request.GET.getlist('search')
+ ).strip()
if query:
if settings.ENABLE_FTS and self.full_text:
queryset = self.apply_full_text(queryset, query)
else:
queryset = queryset.filter(
- Q(code__icontains=query) | Q(name__icontains=query) |
- Q(translations__name__icontains=query, translations__language=self.request.LANGUAGE_CODE))
+ Q(code__icontains=query)
+ | Q(name__icontains=query)
+ | Q(
+ translations__name__icontains=query,
+ translations__language=self.request.LANGUAGE_CODE,
+ )
+ )
self.prepoint_queryset = queryset
if self.point_start is not None:
queryset = queryset.filter(points__gte=self.point_start)
@@ -489,7 +665,9 @@ def get_context_data(self, **kwargs):
context = super(ProblemList, self).get_context_data(**kwargs)
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['has_public_editorial'] = 0 if self.in_contest else int(self.has_public_editorial)
+ context['has_public_editorial'] = (
+ 0 if self.in_contest else int(self.has_public_editorial)
+ )
context['full_text'] = 0 if self.in_contest else int(self.full_text)
context['category'] = self.category
context['categories'] = ProblemGroup.objects.all()
@@ -504,11 +682,21 @@ def get_context_data(self, **kwargs):
context.update(self.get_sort_paginate_context())
if not self.in_contest:
context.update(self.get_sort_context())
- context['hot_problems'] = hot_problems(timedelta(days=1), settings.DMOJ_PROBLEM_HOT_PROBLEM_COUNT)
- context['point_start'], context['point_end'], context['point_values'] = self.get_noui_slider_points()
+ context['hot_problems'] = hot_problems(
+ timedelta(days=1), settings.DMOJ_PROBLEM_HOT_PROBLEM_COUNT
+ )
+ (
+ context['point_start'],
+ context['point_end'],
+ context['point_values'],
+ ) = self.get_noui_slider_points()
else:
context['hot_problems'] = None
- context['point_start'], context['point_end'], context['point_values'] = 0, 0, {}
+ context['point_start'], context['point_end'], context['point_values'] = (
+ 0,
+ 0,
+ {},
+ )
context['hide_contest_scoreboard'] = self.contest.scoreboard_visibility in (
self.contest.SCOREBOARD_AFTER_CONTEST,
self.contest.SCOREBOARD_AFTER_PARTICIPATION,
@@ -517,15 +705,21 @@ def get_context_data(self, **kwargs):
return context
def get_noui_slider_points(self):
- points = sorted(self.prepoint_queryset.values_list('points', flat=True).distinct())
+ points = sorted(
+ self.prepoint_queryset.values_list('points', flat=True).distinct()
+ )
if not points:
return 0, 0, {}
if len(points) == 1:
- return points[0] - 1, points[0] + 1, {
- 'min': points[0] - 1,
- '50%': points[0],
- 'max': points[0] + 1,
- }
+ return (
+ points[0] - 1,
+ points[0] + 1,
+ {
+ 'min': points[0] - 1,
+ '50%': points[0],
+ 'max': points[0] + 1,
+ },
+ )
start, end = points[0], points[-1]
if self.point_start is not None:
@@ -534,7 +728,14 @@ def get_noui_slider_points(self):
end = self.point_end
points_map = {0.0: 'min', 1.0: 'max'}
size = len(points) - 1
- return start, end, {points_map.get(i / size, '%.2f%%' % (100 * i / size,)): j for i, j in enumerate(points)}
+ return (
+ start,
+ end,
+ {
+ points_map.get(i / size, '%.2f%%' % (100 * i / size,)): j
+ for i, j in enumerate(points)
+ },
+ )
def GET_with_session(self, request, key):
if not request.GET:
@@ -545,7 +746,9 @@ def setup_problem_list(self, request):
self.hide_solved = self.GET_with_session(request, 'hide_solved')
self.show_types = self.GET_with_session(request, 'show_types')
self.full_text = self.GET_with_session(request, 'full_text')
- self.has_public_editorial = self.GET_with_session(request, 'has_public_editorial')
+ self.has_public_editorial = self.GET_with_session(
+ request, 'has_public_editorial'
+ )
self.search_query = None
self.category = None
@@ -603,8 +806,14 @@ def get(self, request, *args, **kwargs):
queryset = self.get_normal_queryset()
count = queryset.count()
if not count:
- return HttpResponseRedirect('%s%s%s' % (reverse('problem_list'), request.META['QUERY_STRING'] and '?',
- request.META['QUERY_STRING']))
+ return HttpResponseRedirect(
+ '%s%s%s'
+ % (
+ reverse('problem_list'),
+ request.META['QUERY_STRING'] and '?',
+ request.META['QUERY_STRING'],
+ )
+ )
return HttpResponseRedirect(queryset[randrange(count)].get_absolute_url())
@@ -631,8 +840,11 @@ def remaining_submission_count(self):
# a non-negative integer, which is required for future checks in this view.
return max(
0,
- max_subs - get_contest_submission_count(
- self.object, self.request.profile, self.request.profile.current_contest.virtual,
+ max_subs
+ - get_contest_submission_count(
+ self.object,
+ self.request.profile,
+ self.request.profile.current_contest.virtual,
),
)
@@ -645,7 +857,8 @@ def default_language(self):
def get_content_title(self):
return mark_safe(
- escape(_('Submit to %s')) % format_html(
+ escape(_('Submit to %s'))
+ % format_html(
'{1} ',
reverse('problem_detail', args=[self.object.code]),
self.object.translated_name(self.request.LANGUAGE_CODE),
@@ -653,7 +866,9 @@ def get_content_title(self):
)
def get_title(self):
- return _('Submit to %s') % self.object.translated_name(self.request.LANGUAGE_CODE)
+ return _('Submit to %s') % self.object.translated_name(
+ self.request.LANGUAGE_CODE
+ )
def get_initial(self):
initial = {'language': self.default_language}
@@ -667,7 +882,9 @@ def get_form_kwargs(self):
if self.object.is_editable_by(self.request.user):
kwargs['judge_choices'] = tuple(
- Judge.objects.filter(online=True, problems=self.object).values_list('name', 'name'),
+ Judge.objects.filter(online=True, problems=self.object).values_list(
+ 'name', 'name'
+ ),
)
else:
kwargs['judge_choices'] = ()
@@ -677,9 +894,10 @@ def get_form_kwargs(self):
def get_form(self, form_class=None):
form = super().get_form(form_class)
- form.fields['language'].queryset = (
- self.object.usable_languages.order_by('name', 'key')
- .prefetch_related(Prefetch('runtimeversion_set', RuntimeVersion.objects.order_by('priority')))
+ form.fields['language'].queryset = self.object.usable_languages.order_by(
+ 'name', 'key'
+ ).prefetch_related(
+ Prefetch('runtimeversion_set', RuntimeVersion.objects.order_by('priority'))
)
form_data = getattr(form, 'cleaned_data', form.initial)
@@ -694,21 +912,41 @@ def get_success_url(self):
def form_valid(self, form):
if (
- not self.request.user.has_perm('judge.spam_submission') and
- Submission.objects.filter(user=self.request.profile, rejudged_date__isnull=True)
- .exclude(status__in=['D', 'IE', 'CE', 'AB']).count() >= settings.DMOJ_SUBMISSION_LIMIT
+ not self.request.user.has_perm('judge.spam_submission')
+ and Submission.objects.filter(
+ user=self.request.profile, rejudged_date__isnull=True
+ )
+ .exclude(status__in=['D', 'IE', 'CE', 'AB'])
+ .count()
+ >= settings.DMOJ_SUBMISSION_LIMIT
):
- return HttpResponse(format_html('{0} ', _('You submitted too many submissions.')), status=429)
- if not self.object.allowed_languages.filter(id=form.cleaned_data['language'].id).exists():
+ return HttpResponse(
+ format_html('{0} ', _('You submitted too many submissions.')),
+ status=429,
+ )
+ if not self.object.allowed_languages.filter(
+ id=form.cleaned_data['language'].id
+ ).exists():
raise PermissionDenied()
- if not self.request.user.is_superuser and self.object.banned_users.filter(id=self.request.profile.id).exists():
- return generic_message(self.request, _('Banned from submitting'),
- _('You have been declared persona non grata for this problem. '
- 'You are permanently barred from submitting to this problem.'))
+ if (
+ not self.request.user.is_superuser
+ and self.object.banned_users.filter(id=self.request.profile.id).exists()
+ ):
+ return generic_message(
+ self.request,
+ _('Banned from submitting'),
+ _(
+ 'You have been declared persona non grata for this problem. '
+ 'You are permanently barred from submitting to this problem.'
+ ),
+ )
# Must check for zero and not None. None means infinite submissions remaining.
if self.remaining_submission_count == 0:
- return generic_message(self.request, _('Too many submissions'),
- _('You have exceeded the submission limit for this problem.'))
+ return generic_message(
+ self.request,
+ _('Too many submissions'),
+ _('You have exceeded the submission limit for this problem.'),
+ )
with transaction.atomic():
self.new_submission = form.save(commit=False)
@@ -717,9 +955,13 @@ def form_valid(self, form):
if contest_problem is not None:
# Use the contest object from current_contest.contest because we already use it
# in profile.update_contest().
- self.new_submission.contest_object = self.request.profile.current_contest.contest
+ self.new_submission.contest_object = (
+ self.request.profile.current_contest.contest
+ )
if self.request.profile.current_contest.live:
- self.new_submission.locked_after = self.new_submission.contest_object.locked_after
+ self.new_submission.locked_after = (
+ self.new_submission.contest_object.locked_after
+ )
self.new_submission.save()
ContestSubmission(
submission=self.new_submission,
@@ -729,7 +971,9 @@ def form_valid(self, form):
else:
self.new_submission.save()
- source = SubmissionSource(submission=self.new_submission, source=form.cleaned_data['source'])
+ source = SubmissionSource(
+ submission=self.new_submission, source=form.cleaned_data['source']
+ )
source.save()
# Save a query.
@@ -742,7 +986,9 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['langs'] = Language.objects.all()
context['no_judges'] = not context['form'].fields['language'].queryset
- context['submission_limit'] = self.contest_problem and self.contest_problem.max_submissions
+ context['submission_limit'] = (
+ self.contest_problem and self.contest_problem.max_submissions
+ )
context['submissions_left'] = self.remaining_submission_count
context['ACE_URL'] = settings.ACE_URL
context['default_lang'] = self.default_language
@@ -758,7 +1004,9 @@ def post(self, request, *args, **kwargs):
request.user.username,
kwargs.get(self.slug_url_kwarg),
)
- return HttpResponseForbidden(format_html('{0} ', _('Do you want me to ban you?')))
+ return HttpResponseForbidden(
+ format_html('{0} ', _('Do you want me to ban you?'))
+ )
def dispatch(self, request, *args, **kwargs):
submission_id = kwargs.get('submission')
@@ -767,7 +1015,10 @@ def dispatch(self, request, *args, **kwargs):
Submission.objects.select_related('source', 'language'),
id=submission_id,
)
- if not request.user.has_perm('judge.resubmit_other') and self.old_submission.user != request.profile:
+ if (
+ not request.user.has_perm('judge.resubmit_other')
+ and self.old_submission.user != request.profile
+ ):
raise PermissionDenied()
else:
self.old_submission = None
@@ -775,7 +1026,9 @@ def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
-class ProblemClone(ProblemMixin, PermissionRequiredMixin, TitleMixin, SingleObjectFormView):
+class ProblemClone(
+ ProblemMixin, PermissionRequiredMixin, TitleMixin, SingleObjectFormView
+):
title = gettext_lazy('Clone Problem')
template_name = 'problem/clone.html'
form_class = ProblemCloneForm
@@ -805,4 +1058,6 @@ def form_valid(self, form):
revisions.set_user(self.request.user)
revisions.set_comment(_('Cloned problem from %s') % old_code)
- return HttpResponseRedirect(reverse('admin:judge_problem_change', args=(problem.id,)))
+ return HttpResponseRedirect(
+ reverse('admin:judge_problem_change', args=(problem.id,))
+ )
diff --git a/judge/views/problem_data.py b/judge/views/problem_data.py
index 87aac8b84b..17ae8f4432 100644
--- a/judge/views/problem_data.py
+++ b/judge/views/problem_data.py
@@ -8,7 +8,14 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ValidationError
-from django.forms import BaseModelFormSet, HiddenInput, ModelForm, NumberInput, Select, formset_factory
+from django.forms import (
+ BaseModelFormSet,
+ HiddenInput,
+ ModelForm,
+ NumberInput,
+ Select,
+ formset_factory,
+)
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
@@ -18,7 +25,13 @@
from django.views.generic import DetailView
from judge.highlight_code import highlight_code
-from judge.models import Problem, ProblemData, ProblemTestCase, Submission, problem_data_storage
+from judge.models import (
+ Problem,
+ ProblemData,
+ ProblemTestCase,
+ Submission,
+ problem_data_storage,
+)
from judge.utils.problem_data import ProblemDataCompiler
from judge.utils.unicode import utf8text
from judge.utils.views import TitleMixin, add_file_response
@@ -62,8 +75,16 @@ def clean_generator(self):
class Meta:
model = ProblemData
- fields = ['zipfile', 'generator', 'unicode', 'nobigmath', 'output_limit', 'output_prefix',
- 'checker', 'checker_args']
+ fields = [
+ 'zipfile',
+ 'generator',
+ 'unicode',
+ 'nobigmath',
+ 'output_limit',
+ 'output_prefix',
+ 'checker',
+ 'checker_args',
+ ]
widgets = {
'checker_args': HiddenInput,
}
@@ -74,8 +95,19 @@ class ProblemCaseForm(ModelForm):
class Meta:
model = ProblemTestCase
- fields = ('order', 'type', 'input_file', 'output_file', 'points',
- 'is_pretest', 'output_limit', 'output_prefix', 'checker', 'checker_args', 'generator_args')
+ fields = (
+ 'order',
+ 'type',
+ 'input_file',
+ 'output_file',
+ 'points',
+ 'is_pretest',
+ 'output_limit',
+ 'output_prefix',
+ 'checker',
+ 'checker_args',
+ 'generator_args',
+ )
widgets = {
'generator_args': HiddenInput,
'type': Select(attrs={'style': 'width: 100%'}),
@@ -86,8 +118,11 @@ class Meta:
}
-class ProblemCaseFormSet(formset_factory(ProblemCaseForm, formset=BaseModelFormSet, extra=1, max_num=1,
- can_delete=True)):
+class ProblemCaseFormSet(
+ formset_factory(
+ ProblemCaseForm, formset=BaseModelFormSet, extra=1, max_num=1, can_delete=True
+ )
+):
model = ProblemTestCase
def __init__(self, *args, **kwargs):
@@ -117,9 +152,15 @@ def get_title(self):
return _('Comparing submissions for {0}').format(self.object.name)
def get_content_title(self):
- return mark_safe(escape(_('Comparing submissions for {0}')).format(
- format_html('{0} ', self.object.name, reverse('problem_detail', args=[self.object.code])),
- ))
+ return mark_safe(
+ escape(_('Comparing submissions for {0}')).format(
+ format_html(
+ '{0} ',
+ self.object.name,
+ reverse('problem_detail', args=[self.object.code]),
+ ),
+ )
+ )
def get_object(self, queryset=None):
problem = super(ProblemSubmissionDiff, self).get_object(queryset)
@@ -156,18 +197,34 @@ def get_title(self):
return _('Editing data for {0}').format(self.object.name)
def get_content_title(self):
- return mark_safe(escape(_('Editing data for %s')) % (
- format_html('{0} ', self.object.name,
- reverse('problem_detail', args=[self.object.code]))))
+ return mark_safe(
+ escape(_('Editing data for %s'))
+ % (
+ format_html(
+ '{0} ',
+ self.object.name,
+ reverse('problem_detail', args=[self.object.code]),
+ )
+ )
+ )
def get_data_form(self, post=False):
- return ProblemDataForm(data=self.request.POST if post else None, prefix='problem-data',
- files=self.request.FILES if post else None,
- instance=ProblemData.objects.get_or_create(problem=self.object)[0])
+ return ProblemDataForm(
+ data=self.request.POST if post else None,
+ prefix='problem-data',
+ files=self.request.FILES if post else None,
+ instance=ProblemData.objects.get_or_create(problem=self.object)[0],
+ )
def get_case_formset(self, files, post=False):
- return ProblemCaseFormSet(data=self.request.POST if post else None, prefix='cases', valid_files=files,
- queryset=ProblemTestCase.objects.filter(dataset_id=self.object.pk).order_by('order'))
+ return ProblemCaseFormSet(
+ data=self.request.POST if post else None,
+ prefix='cases',
+ valid_files=files,
+ queryset=ProblemTestCase.objects.filter(dataset_id=self.object.pk).order_by(
+ 'order'
+ ),
+ )
def get_valid_files(self, data, post=False):
try:
@@ -185,12 +242,16 @@ def get_context_data(self, **kwargs):
context = super(ProblemDataView, self).get_context_data(**kwargs)
if 'data_form' not in context:
context['data_form'] = self.get_data_form()
- valid_files = context['valid_files'] = self.get_valid_files(context['data_form'].instance)
+ valid_files = context['valid_files'] = self.get_valid_files(
+ context['data_form'].instance
+ )
context['cases_formset'] = self.get_case_formset(valid_files)
if context['valid_files']:
context['valid_files_json'] = mark_safe(json.dumps(context['valid_files']))
context['valid_files'] = set(context['valid_files'])
- context['all_case_forms'] = chain(context['cases_formset'], [context['cases_formset'].empty_form])
+ context['all_case_forms'] = chain(
+ context['cases_formset'], [context['cases_formset'].empty_form]
+ )
return context
def post(self, request, *args, **kwargs):
@@ -206,10 +267,17 @@ def post(self, request, *args, **kwargs):
case.save()
for case in cases_formset.deleted_objects:
case.delete()
- ProblemDataCompiler.generate(problem, data, problem.cases.order_by('order'), valid_files)
+ ProblemDataCompiler.generate(
+ problem, data, problem.cases.order_by('order'), valid_files
+ )
return HttpResponseRedirect(request.get_full_path())
- return self.render_to_response(self.get_context_data(data_form=data_form, cases_formset=cases_formset,
- valid_files=valid_files))
+ return self.render_to_response(
+ self.get_context_data(
+ data_form=data_form,
+ cases_formset=cases_formset,
+ valid_files=valid_files,
+ )
+ )
put = post
@@ -221,7 +289,12 @@ def problem_data_file(request, problem, path):
raise Http404()
problem_dir = problem_data_storage.path(problem)
- if os.path.commonpath((problem_data_storage.path(os.path.join(problem, path)), problem_dir)) != problem_dir:
+ if (
+ os.path.commonpath(
+ (problem_data_storage.path(os.path.join(problem, path)), problem_dir)
+ )
+ != problem_dir
+ ):
raise Http404()
response = HttpResponse()
@@ -232,7 +305,13 @@ def problem_data_file(request, problem, path):
url_path = None
try:
- add_file_response(request, response, url_path, os.path.join(problem, path), problem_data_storage)
+ add_file_response(
+ request,
+ response,
+ url_path,
+ os.path.join(problem, path),
+ problem_data_storage,
+ )
except IOError:
raise Http404()
@@ -247,15 +326,29 @@ def problem_init_view(request, problem):
raise Http404()
try:
- with problem_data_storage.open(os.path.join(problem.code, 'init.yml'), 'rb') as f:
+ with problem_data_storage.open(
+ os.path.join(problem.code, 'init.yml'), 'rb'
+ ) as f:
data = utf8text(f.read()).rstrip('\n')
except IOError:
raise Http404()
- return render(request, 'problem/yaml.html', {
- 'raw_source': data, 'highlighted_source': highlight_code(data, 'yaml'),
- 'title': _('Generated init.yml for %s') % problem.name,
- 'content_title': mark_safe(escape(_('Generated init.yml for %s')) % (
- format_html('{0} ', problem.name,
- reverse('problem_detail', args=[problem.code])))),
- })
+ return render(
+ request,
+ 'problem/yaml.html',
+ {
+ 'raw_source': data,
+ 'highlighted_source': highlight_code(data, 'yaml'),
+ 'title': _('Generated init.yml for %s') % problem.name,
+ 'content_title': mark_safe(
+ escape(_('Generated init.yml for %s'))
+ % (
+ format_html(
+ '{0} ',
+ problem.name,
+ reverse('problem_detail', args=[problem.code]),
+ )
+ )
+ ),
+ },
+ )
diff --git a/judge/views/problem_manage.py b/judge/views/problem_manage.py
index 5857b0cc55..4a0c9f7353 100644
--- a/judge/views/problem_manage.py
+++ b/judge/views/problem_manage.py
@@ -3,7 +3,12 @@
from celery.result import AsyncResult
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
-from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
+from django.http import (
+ Http404,
+ HttpResponse,
+ HttpResponseBadRequest,
+ HttpResponseRedirect,
+)
from django.urls import reverse
from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe
@@ -50,20 +55,33 @@ def get_title(self):
return _('Managing submissions for %s') % (self.object.name,)
def get_content_title(self):
- return mark_safe(escape(_('Managing submissions for %s')) % (
- format_html('{0} ', self.object.name,
- reverse('problem_detail', args=[self.object.code]))))
+ return mark_safe(
+ escape(_('Managing submissions for %s'))
+ % (
+ format_html(
+ '{0} ',
+ self.object.name,
+ reverse('problem_detail', args=[self.object.code]),
+ )
+ )
+ )
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['submission_count'] = self.object.submission_set.count()
- context['languages'] = [(lang_id, short_name or key) for lang_id, key, short_name in
- Language.objects.values_list('id', 'key', 'short_name')]
+ context['languages'] = [
+ (lang_id, short_name or key)
+ for lang_id, key, short_name in Language.objects.values_list(
+ 'id', 'key', 'short_name'
+ )
+ ]
context['results'] = sorted(map(itemgetter(0), Submission.RESULT))
return context
-class BaseRejudgeSubmissionsView(PermissionRequiredMixin, ManageProblemSubmissionActionMixin, BaseDetailView):
+class BaseRejudgeSubmissionsView(
+ PermissionRequiredMixin, ManageProblemSubmissionActionMixin, BaseDetailView
+):
permission_required = 'judge.rejudge_submission_lot'
def perform_action(self):
@@ -82,7 +100,9 @@ def perform_action(self):
except ValueError:
return HttpResponseBadRequest()
- return self.generate_response(id_range, languages, self.request.POST.getlist('result'))
+ return self.generate_response(
+ id_range, languages, self.request.POST.getlist('result')
+ )
def generate_response(self, id_range, languages, results):
raise NotImplementedError()
@@ -90,17 +110,24 @@ def generate_response(self, id_range, languages, results):
class RejudgeSubmissionsView(BaseRejudgeSubmissionsView):
def generate_response(self, id_range, languages, results):
- status = rejudge_problem_filter.delay(self.object.id, id_range, languages, results,
- user_id=self.request.user.id)
+ status = rejudge_problem_filter.delay(
+ self.object.id, id_range, languages, results, user_id=self.request.user.id
+ )
return redirect_to_task_status(
- status, message=_('Rejudging selected submissions for %s...') % (self.object.name,),
- redirect=reverse('problem_submissions_rejudge_success', args=[self.object.code, status.id]),
+ status,
+ message=_('Rejudging selected submissions for %s...') % (self.object.name,),
+ redirect=reverse(
+ 'problem_submissions_rejudge_success',
+ args=[self.object.code, status.id],
+ ),
)
class PreviewRejudgeSubmissionsView(BaseRejudgeSubmissionsView):
def generate_response(self, id_range, languages, results):
- queryset = apply_submission_filter(self.object.submission_set.all(), id_range, languages, results)
+ queryset = apply_submission_filter(
+ self.object.submission_set.all(), id_range, languages, results
+ )
return HttpResponse(str(queryset.count()))
@@ -108,8 +135,12 @@ class RescoreAllSubmissionsView(ManageProblemSubmissionActionMixin, BaseDetailVi
def perform_action(self):
status = rescore_problem.delay(self.object.id)
return redirect_to_task_status(
- status, message=_('Rescoring all submissions for %s...') % (self.object.name,),
- redirect=reverse('problem_submissions_rescore_success', args=[self.object.code, status.id]),
+ status,
+ message=_('Rescoring all submissions for %s...') % (self.object.name,),
+ redirect=reverse(
+ 'problem_submissions_rescore_success',
+ args=[self.object.code, status.id],
+ ),
)
@@ -117,8 +148,15 @@ def rejudge_success(request, problem, task_id):
count = AsyncResult(task_id).result
if not isinstance(count, int):
raise Http404()
- messages.success(request, ngettext('Successfully scheduled %d submission for rejudging.',
- 'Successfully scheduled %d submissions for rejudging.', count) % (count,))
+ messages.success(
+ request,
+ ngettext(
+ 'Successfully scheduled %d submission for rejudging.',
+ 'Successfully scheduled %d submissions for rejudging.',
+ count,
+ )
+ % (count,),
+ )
return HttpResponseRedirect(reverse('problem_manage_submissions', args=[problem]))
@@ -126,6 +164,13 @@ def rescore_success(request, problem, task_id):
count = AsyncResult(task_id).result
if not isinstance(count, int):
raise Http404()
- messages.success(request, ngettext('%d submission was successfully rescored.',
- '%d submissions were successfully rescored.', count) % (count,))
+ messages.success(
+ request,
+ ngettext(
+ '%d submission was successfully rescored.',
+ '%d submissions were successfully rescored.',
+ count,
+ )
+ % (count,),
+ )
return HttpResponseRedirect(reverse('problem_manage_submissions', args=[problem]))
diff --git a/judge/views/ranked_submission.py b/judge/views/ranked_submission.py
index d2f9473fb7..b1bcd4110b 100644
--- a/judge/views/ranked_submission.py
+++ b/judge/views/ranked_submission.py
@@ -28,13 +28,21 @@ def get_queryset(self):
constraint = ''
if self.selected_languages:
- lang_ids = Language.objects.filter(key__in=self.selected_languages).values_list('id', flat=True)
+ lang_ids = Language.objects.filter(
+ key__in=self.selected_languages
+ ).values_list('id', flat=True)
if lang_ids:
- constraint += f' AND sub.language_id IN ({", ".join(["%s"] * len(lang_ids))})'
+ constraint += (
+ f' AND sub.language_id IN ({", ".join(["%s"] * len(lang_ids))})'
+ )
params.extend(lang_ids)
self.selected_languages = set()
- queryset = super(RankedSubmissions, self).get_queryset().filter(user__is_unlisted=False)
+ queryset = (
+ super(RankedSubmissions, self)
+ .get_queryset()
+ .filter(user__is_unlisted=False)
+ )
join_sql_subquery(
queryset,
subquery="""
@@ -54,8 +62,13 @@ def get_queryset(self):
ON (sub.user_id = fastest.uid AND sub.time = fastest.time)
WHERE sub.problem_id = %s {constraint}
GROUP BY sub.user_id
- """.format(points=points, contest_join=contest_join, constraint=constraint),
- params=params * 3, alias='best_subs', join_fields=[('id', 'id')], related_model=Submission,
+ """.format(
+ points=points, contest_join=contest_join, constraint=constraint
+ ),
+ params=params * 3,
+ alias='best_subs',
+ join_fields=[('id', 'id')],
+ related_model=Submission,
)
if self.in_contest:
@@ -67,10 +80,16 @@ def get_title(self):
return _('Best solutions for %s') % self.problem_name
def get_content_title(self):
- return mark_safe(escape(_('Best solutions for %s')) % (
- format_html('{0} ', self.problem_name,
- reverse('problem_detail', args=[self.problem.code])),
- ))
+ return mark_safe(
+ escape(_('Best solutions for %s'))
+ % (
+ format_html(
+ '{0} ',
+ self.problem_name,
+ reverse('problem_detail', args=[self.problem.code]),
+ ),
+ )
+ )
def _get_result_data(self, queryset=None):
if queryset is None:
@@ -82,25 +101,42 @@ 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,
+ '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,
+ '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 mark_safe(escape(_('Best solutions for %(problem)s in %(contest)s')) % {
- 'problem': format_html('{0} ', self.problem_name,
- reverse('problem_detail', args=[self.problem.code])),
- 'contest': format_html('{0} ', self.contest.name,
- reverse('contest_view', args=[self.contest.key])),
- })
- return mark_safe(escape(_('Best solutions for problem %(number)s in %(contest)s')) % {
- 'number': self.get_problem_number(self.problem),
- 'contest': format_html('{0} ', self.contest.name,
- reverse('contest_view', args=[self.contest.key])),
- })
+ return mark_safe(
+ escape(_('Best solutions for %(problem)s in %(contest)s'))
+ % {
+ 'problem': format_html(
+ '{0} ',
+ self.problem_name,
+ reverse('problem_detail', args=[self.problem.code]),
+ ),
+ 'contest': format_html(
+ '{0} ',
+ self.contest.name,
+ reverse('contest_view', args=[self.contest.key]),
+ ),
+ }
+ )
+ return mark_safe(
+ escape(_('Best solutions for problem %(number)s in %(contest)s'))
+ % {
+ 'number': self.get_problem_number(self.problem),
+ 'contest': format_html(
+ '{0} ',
+ self.contest.name,
+ reverse('contest_view', args=[self.contest.key]),
+ ),
+ }
+ )
def _get_queryset(self):
return super()._get_queryset().filter(contest_object=self.contest)
diff --git a/judge/views/register.py b/judge/views/register.py
index f5cdd64029..f0b545c0e3 100644
--- a/judge/views/register.py
+++ b/judge/views/register.py
@@ -6,8 +6,10 @@
from django.forms import ChoiceField, ModelChoiceField
from django.shortcuts import render
from django.utils.translation import gettext, gettext_lazy as _, ngettext
-from registration.backends.default.views import (ActivationView as OldActivationView,
- RegistrationView as OldRegistrationView)
+from registration.backends.default.views import (
+ ActivationView as OldActivationView,
+ RegistrationView as OldRegistrationView,
+)
from registration.forms import RegistrationForm
from sortedm2m.forms import SortedMultipleChoiceField
@@ -19,27 +21,49 @@
class CustomRegistrationForm(RegistrationForm):
- username = forms.RegexField(regex=r'^\w+$', max_length=30, label=_('Username'),
- error_messages={'invalid': _('A username must contain letters, '
- 'numbers, or underscores.')})
- timezone = ChoiceField(label=_('Timezone'), choices=TIMEZONE,
- widget=Select2Widget(attrs={'style': 'width:100%'}))
- language = ModelChoiceField(queryset=Language.objects.all(), label=_('Preferred language'), empty_label=None,
- widget=Select2Widget(attrs={'style': 'width:100%'}))
- organizations = SortedMultipleChoiceField(queryset=Organization.objects.filter(is_open=True),
- label=_('Organizations'), required=False,
- widget=Select2MultipleWidget(attrs={'style': 'width:100%'}))
+ username = forms.RegexField(
+ regex=r'^\w+$',
+ max_length=30,
+ label=_('Username'),
+ error_messages={
+ 'invalid': _('A username must contain letters, ' 'numbers, or underscores.')
+ },
+ )
+ timezone = ChoiceField(
+ label=_('Timezone'),
+ choices=TIMEZONE,
+ widget=Select2Widget(attrs={'style': 'width:100%'}),
+ )
+ language = ModelChoiceField(
+ queryset=Language.objects.all(),
+ label=_('Preferred language'),
+ empty_label=None,
+ widget=Select2Widget(attrs={'style': 'width:100%'}),
+ )
+ organizations = SortedMultipleChoiceField(
+ queryset=Organization.objects.filter(is_open=True),
+ label=_('Organizations'),
+ required=False,
+ widget=Select2MultipleWidget(attrs={'style': 'width:100%'}),
+ )
if newsletter_id is not None:
- newsletter = forms.BooleanField(label=_('Subscribe to newsletter?'), initial=True, required=False)
+ newsletter = forms.BooleanField(
+ label=_('Subscribe to newsletter?'), initial=True, required=False
+ )
if ReCaptchaField is not None:
captcha = ReCaptchaField(widget=ReCaptchaWidget())
def clean_email(self):
if User.objects.filter(email=self.cleaned_data['email']).exists():
- raise forms.ValidationError(gettext('The email address "%s" is already taken. Only one registration '
- 'is allowed per address.') % self.cleaned_data['email'])
+ raise forms.ValidationError(
+ gettext(
+ 'The email address "%s" is already taken. Only one registration '
+ 'is allowed per address.'
+ )
+ % self.cleaned_data['email']
+ )
validate_email_domain(self.cleaned_data['email'])
return self.cleaned_data['email']
@@ -47,9 +71,13 @@ def clean_organizations(self):
organizations = self.cleaned_data.get('organizations') or []
max_orgs = settings.DMOJ_USER_MAX_ORGANIZATION_COUNT
if len(organizations) > max_orgs:
- raise forms.ValidationError(ngettext('You may not be part of more than {count} public organization.',
- 'You may not be part of more than {count} public organizations.',
- max_orgs).format(count=max_orgs))
+ raise forms.ValidationError(
+ ngettext(
+ 'You may not be part of more than {count} public organization.',
+ 'You may not be part of more than {count} public organizations.',
+ max_orgs,
+ ).format(count=max_orgs)
+ )
return self.cleaned_data['organizations']
@@ -68,9 +96,12 @@ def get_context_data(self, **kwargs):
def register(self, form):
user = super(RegistrationView, self).register(form)
- profile, _ = Profile.objects.get_or_create(user=user, defaults={
- 'language': Language.get_default_language(),
- })
+ profile, _ = Profile.objects.get_or_create(
+ user=user,
+ defaults={
+ 'language': Language.get_default_language(),
+ },
+ )
cleaned_data = form.cleaned_data
profile.timezone = cleaned_data['timezone']
@@ -100,7 +131,11 @@ def get_context_data(self, **kwargs):
def social_auth_error(request):
- return render(request, 'generic-message.html', {
- 'title': gettext('Authentication failure'),
- 'message': request.GET.get('message'),
- })
+ return render(
+ request,
+ 'generic-message.html',
+ {
+ 'title': gettext('Authentication failure'),
+ 'message': request.GET.get('message'),
+ },
+ )
diff --git a/judge/views/select2.py b/judge/views/select2.py
index 2b7fb47698..98955308e0 100644
--- a/judge/views/select2.py
+++ b/judge/views/select2.py
@@ -26,14 +26,18 @@ def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
context = self.get_context_data()
- return JsonResponse({
- 'results': [
- {
- 'text': smart_str(self.get_name(obj)),
- 'id': obj.pk,
- } for obj in context['object_list']],
- 'more': context['page_obj'].has_next(),
- })
+ return JsonResponse(
+ {
+ 'results': [
+ {
+ 'text': smart_str(self.get_name(obj)),
+ 'id': obj.pk,
+ }
+ for obj in context['object_list']
+ ],
+ 'more': context['page_obj'].has_next(),
+ }
+ )
def get_name(self, obj):
return str(obj)
@@ -41,7 +45,11 @@ def get_name(self, obj):
class UserSelect2View(Select2View):
def get_queryset(self):
- return _get_user_queryset(self.term).annotate(username=F('user__username')).only('id')
+ return (
+ _get_user_queryset(self.term)
+ .annotate(username=F('user__username'))
+ .only('id')
+ )
def get_name(self, obj):
return obj.username
@@ -54,19 +62,23 @@ def get_queryset(self):
class ClassSelect2View(Select2View):
def get_queryset(self):
- return Class.get_visible_classes(self.request.user).filter(name__icontains=self.term)
+ return Class.get_visible_classes(self.request.user).filter(
+ name__icontains=self.term
+ )
class ProblemSelect2View(Select2View):
def get_queryset(self):
- return Problem.get_visible_problems(self.request.user) \
- .filter(Q(code__icontains=self.term) | Q(name__icontains=self.term))
+ return Problem.get_visible_problems(self.request.user).filter(
+ Q(code__icontains=self.term) | Q(name__icontains=self.term)
+ )
class ContestSelect2View(Select2View):
def get_queryset(self):
- return Contest.get_visible_contests(self.request.user) \
- .filter(Q(key__icontains=self.term) | Q(name__icontains=self.term))
+ return Contest.get_visible_contests(self.request.user).filter(
+ Q(key__icontains=self.term) | Q(name__icontains=self.term)
+ )
class CommentSelect2View(Select2View):
@@ -87,21 +99,34 @@ def get(self, request, *args, **kwargs):
self.gravatar_size = request.GET.get('gravatar_size', 128)
self.gravatar_default = request.GET.get('gravatar_default', None)
- self.object_list = self.get_queryset().values_list('pk', 'user__username', 'user__email', 'display_rank',
- 'username_display_override')
+ self.object_list = self.get_queryset().values_list(
+ 'pk',
+ 'user__username',
+ 'user__email',
+ 'display_rank',
+ 'username_display_override',
+ )
context = self.get_context_data()
- return JsonResponse({
- 'results': [
- {
- 'text': username_override or username,
- 'id': username,
- 'gravatar_url': gravatar(email, self.gravatar_size, self.gravatar_default),
- 'display_rank': display_rank,
- } for pk, username, email, display_rank, username_override in context['object_list']],
- 'more': context['page_obj'].has_next(),
- })
+ return JsonResponse(
+ {
+ 'results': [
+ {
+ 'text': username_override or username,
+ 'id': username,
+ 'gravatar_url': gravatar(
+ email, self.gravatar_size, self.gravatar_default
+ ),
+ 'display_rank': display_rank,
+ }
+ for pk, username, email, display_rank, username_override in context[
+ 'object_list'
+ ]
+ ],
+ 'more': context['page_obj'].has_next(),
+ }
+ )
def get_name(self, obj):
return str(obj)
@@ -110,20 +135,25 @@ def get_name(self, obj):
class ContestUserSearchSelect2View(UserSearchSelect2View):
def get_queryset(self):
contest = get_object_or_404(Contest, key=self.kwargs['contest'])
- if not contest.is_accessible_by(self.request.user) or not contest.can_see_full_scoreboard(self.request.user):
+ if not contest.is_accessible_by(
+ self.request.user
+ ) or not contest.can_see_full_scoreboard(self.request.user):
raise Http404()
- return Profile.objects.filter(contest_history__contest=contest,
- user__username__icontains=self.term).distinct()
+ return Profile.objects.filter(
+ contest_history__contest=contest, user__username__icontains=self.term
+ ).distinct()
class TicketUserSelect2View(UserSearchSelect2View):
def get_queryset(self):
- return Profile.objects.filter(tickets__isnull=False,
- user__username__icontains=self.term).distinct()
+ return Profile.objects.filter(
+ tickets__isnull=False, user__username__icontains=self.term
+ ).distinct()
class AssigneeSelect2View(UserSearchSelect2View):
def get_queryset(self):
- return Profile.objects.filter(assigned_tickets__isnull=False,
- user__username__icontains=self.term).distinct()
+ return Profile.objects.filter(
+ assigned_tickets__isnull=False, user__username__icontains=self.term
+ ).distinct()
diff --git a/judge/views/stats.py b/judge/views/stats.py
index 9cfdfd273b..701a4b2d5b 100644
--- a/judge/views/stats.py
+++ b/judge/views/stats.py
@@ -8,27 +8,42 @@
from django.utils.translation import gettext as _
from judge.models import Language, Submission
-from judge.utils.stats import chart_colors, get_bar_chart, get_pie_chart, highlight_colors
+from judge.utils.stats import (
+ chart_colors,
+ get_bar_chart,
+ get_pie_chart,
+ highlight_colors,
+)
ac_count = Count(Value(1), filter=Q(submission__result='AC'))
-def language_data(request, language_count=Language.objects.annotate(count=Count('submission'))):
- languages = language_count.filter(count__gt=0).values('name', 'count').order_by('-count')
+def language_data(
+ request, language_count=Language.objects.annotate(count=Count('submission'))
+):
+ languages = (
+ language_count.filter(count__gt=0).values('name', 'count').order_by('-count')
+ )
num_languages = min(len(languages), settings.DMOJ_STATS_LANGUAGE_THRESHOLD)
other_count = sum(map(itemgetter('count'), languages[num_languages:]))
- return JsonResponse({
- 'labels': list(map(itemgetter('name'), languages[:num_languages])) + [_('Other')],
- 'datasets': [
- {
- 'backgroundColor': chart_colors[:num_languages] + ['#FDB45C'],
- 'highlightBackgroundColor': highlight_colors[:num_languages] + ['#FFC870'],
- 'data': list(map(itemgetter('count'), languages[:num_languages])) + [other_count],
- },
- ],
- }, safe=False)
+ return JsonResponse(
+ {
+ 'labels': list(map(itemgetter('name'), languages[:num_languages]))
+ + [_('Other')],
+ 'datasets': [
+ {
+ 'backgroundColor': chart_colors[:num_languages] + ['#FDB45C'],
+ 'highlightBackgroundColor': highlight_colors[:num_languages]
+ + ['#FFC870'],
+ 'data': list(map(itemgetter('count'), languages[:num_languages]))
+ + [other_count],
+ },
+ ],
+ },
+ safe=False,
+ )
def ac_language_data(request):
@@ -37,8 +52,12 @@ def ac_language_data(request):
def status_data(request, statuses=None):
if not statuses:
- statuses = (Submission.objects.values('result').annotate(count=Count('result'))
- .values('result', 'count').order_by('-count'))
+ statuses = (
+ Submission.objects.values('result')
+ .annotate(count=Count('result'))
+ .values('result', 'count')
+ .order_by('-count')
+ )
data = []
for status in statuses:
res = status['result']
@@ -51,13 +70,24 @@ def status_data(request, statuses=None):
def ac_rate(request):
- rate = CombinedExpression(ac_count / Count('submission'), '*', Value(100.0), output_field=FloatField())
- data = Language.objects.annotate(total=Count('submission'), ac_rate=rate).filter(total__gt=0) \
- .order_by('total').values_list('name', 'ac_rate')
+ rate = CombinedExpression(
+ ac_count / Count('submission'), '*', Value(100.0), output_field=FloatField()
+ )
+ data = (
+ Language.objects.annotate(total=Count('submission'), ac_rate=rate)
+ .filter(total__gt=0)
+ .order_by('total')
+ .values_list('name', 'ac_rate')
+ )
return JsonResponse(get_bar_chart(list(data)))
def language(request):
- return render(request, 'stats/language.html', {
- 'title': _('Language statistics'), 'tab': 'language',
- })
+ return render(
+ request,
+ 'stats/language.html',
+ {
+ 'title': _('Language statistics'),
+ 'tab': 'language',
+ },
+ )
diff --git a/judge/views/status.py b/judge/views/status.py
index 9e11603860..5a0f948760 100644
--- a/judge/views/status.py
+++ b/judge/views/status.py
@@ -19,21 +19,29 @@ def get_judges(request):
def status_all(request):
see_all, judges = get_judges(request)
- return render(request, 'status/judge-status.html', {
- 'title': _('Status'),
- 'judges': judges,
- 'runtime_version_data': Judge.runtime_versions(),
- 'see_all_judges': see_all,
- })
+ return render(
+ request,
+ 'status/judge-status.html',
+ {
+ 'title': _('Status'),
+ 'judges': judges,
+ 'runtime_version_data': Judge.runtime_versions(),
+ 'see_all_judges': see_all,
+ },
+ )
def status_table(request):
see_all, judges = get_judges(request)
- return render(request, 'status/judge-status-table.html', {
- 'judges': judges,
- 'runtime_version_data': Judge.runtime_versions(),
- 'see_all_judges': see_all,
- })
+ return render(
+ request,
+ 'status/judge-status-table.html',
+ {
+ 'judges': judges,
+ 'runtime_version_data': Judge.runtime_versions(),
+ 'see_all_judges': see_all,
+ },
+ )
class LatestList(list):
@@ -62,7 +70,9 @@ def version_matrix(request):
judges = {judge.id: judge.name for judge in Judge.objects.filter(online=True)}
languages = Language.objects.filter(judges__online=True).distinct()
- for runtime in RuntimeVersion.objects.filter(judge__online=True).order_by('priority'):
+ for runtime in RuntimeVersion.objects.filter(judge__online=True).order_by(
+ 'priority'
+ ):
matrix[runtime.judge_id][runtime.language_id].append(runtime)
for judge, data in matrix.items():
@@ -104,9 +114,13 @@ def version_matrix(request):
versions.is_latest = versions.versions == latest[language]
languages = sorted(languages, key=lambda lang: version.parse(lang.name))
- return render(request, 'status/versions.html', {
- 'title': _('Version'),
- 'judges': sorted(matrix.keys()),
- 'languages': languages,
- 'matrix': matrix,
- })
+ return render(
+ request,
+ 'status/versions.html',
+ {
+ 'title': _('Version'),
+ 'judges': sorted(matrix.keys()),
+ 'languages': languages,
+ 'matrix': matrix,
+ },
+ )
diff --git a/judge/views/submission.py b/judge/views/submission.py
index d62243788a..ae1a58fe04 100644
--- a/judge/views/submission.py
+++ b/judge/views/submission.py
@@ -6,9 +6,19 @@
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.cache import cache
-from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied
+from django.core.exceptions import (
+ ImproperlyConfigured,
+ ObjectDoesNotExist,
+ PermissionDenied,
+)
from django.db.models import Prefetch, Q
-from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
+from django.http import (
+ Http404,
+ HttpResponse,
+ HttpResponseBadRequest,
+ HttpResponseRedirect,
+ JsonResponse,
+)
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils import timezone
@@ -21,22 +31,56 @@
from judge import event_poster as event
from judge.highlight_code import highlight_code
-from judge.models import Contest, Language, Problem, ProblemTranslation, Profile, Submission
+from judge.models import (
+ Contest,
+ Language,
+ Problem,
+ ProblemTranslation,
+ Profile,
+ Submission,
+)
from judge.models.problem import SubmissionSourceAccess
from judge.utils.infinite_paginator import InfinitePaginationMixin
from judge.utils.lazy import memo_lazy
-from judge.utils.problems import get_result_data, user_completed_ids, user_editable_ids, user_tester_ids
+from judge.utils.problems import (
+ get_result_data,
+ user_completed_ids,
+ user_editable_ids,
+ user_tester_ids,
+)
from judge.utils.raw_sql import join_sql_subquery, use_straight_join
from judge.utils.views import DiggPaginatorMixin, TitleMixin, generic_message
def submission_related(queryset):
- return queryset.select_related('user__user', 'problem', 'language') \
- .only('id', 'user__user__username', 'user__display_rank', 'user__rating', 'problem__name',
- 'problem__code', 'problem__is_public', 'language__short_name', 'language__key', 'date', 'time', 'memory',
- 'points', 'result', 'status', 'case_points', 'case_total', 'current_testcase', 'contest_object',
- 'locked_after', 'problem__submission_source_visibility_mode', 'user__username_display_override') \
+ return (
+ queryset.select_related('user__user', 'problem', 'language')
+ .only(
+ 'id',
+ 'user__user__username',
+ 'user__display_rank',
+ 'user__rating',
+ 'problem__name',
+ 'problem__code',
+ 'problem__is_public',
+ 'language__short_name',
+ 'language__key',
+ 'date',
+ 'time',
+ 'memory',
+ 'points',
+ 'result',
+ 'status',
+ 'case_points',
+ 'case_total',
+ 'current_testcase',
+ 'contest_object',
+ 'locked_after',
+ 'problem__submission_source_visibility_mode',
+ 'user__username_display_override',
+ )
.prefetch_related('contest_object__authors', 'contest_object__curators')
+ )
class SubmissionPermissionDenied(PermissionDenied):
@@ -65,17 +109,32 @@ def get(self, request, *args, **kwargs):
def no_permission(self, submission):
problem = submission.problem
- if problem.is_accessible_by(self.request.user) and \
- problem.submission_source_visibility == SubmissionSourceAccess.SOLVED:
-
- message = escape(_('Permission denied. Solve %(problem)s in order to view it.')) % {
- 'problem': format_html('{1} ',
- reverse('problem_detail', args=[problem.code]),
- problem.translated_name(self.request.LANGUAGE_CODE)),
+ if (
+ problem.is_accessible_by(self.request.user)
+ and problem.submission_source_visibility == SubmissionSourceAccess.SOLVED
+ ):
+ message = escape(
+ _('Permission denied. Solve %(problem)s in order to view it.')
+ ) % {
+ 'problem': format_html(
+ '{1} ',
+ reverse('problem_detail', args=[problem.code]),
+ problem.translated_name(self.request.LANGUAGE_CODE),
+ ),
}
- return generic_message(self.request, _("Can't access submission"), mark_safe(message), status=403)
+ return generic_message(
+ self.request,
+ _("Can't access submission"),
+ mark_safe(message),
+ status=403,
+ )
else:
- return generic_message(self.request, _("Can't access submission"), _('Permission denied.'), status=403)
+ return generic_message(
+ self.request,
+ _("Can't access submission"),
+ _('Permission denied.'),
+ status=403,
+ )
def get_title(self):
submission = self.object
@@ -86,14 +145,21 @@ def get_title(self):
def get_content_title(self):
submission = self.object
- return mark_safe(escape(_('Submission of %(problem)s by %(user)s')) % {
- 'problem': format_html('{1} ',
- reverse('problem_detail', args=[submission.problem.code]),
- submission.problem.translated_name(self.request.LANGUAGE_CODE)),
- 'user': format_html('{1} ',
- reverse('user_page', args=[submission.user.user.username]),
- submission.user.display_name),
- })
+ return mark_safe(
+ escape(_('Submission of %(problem)s by %(user)s'))
+ % {
+ 'problem': format_html(
+ '{1} ',
+ reverse('problem_detail', args=[submission.problem.code]),
+ submission.problem.translated_name(self.request.LANGUAGE_CODE),
+ ),
+ 'user': format_html(
+ '{1} ',
+ reverse('user_page', args=[submission.user.user.username]),
+ submission.user.display_name,
+ ),
+ }
+ )
class SubmissionSource(SubmissionDetailBase):
@@ -106,7 +172,9 @@ def get_context_data(self, **kwargs):
context = super(SubmissionSource, self).get_context_data(**kwargs)
submission = self.object
context['raw_source'] = submission.source.source.rstrip('\n')
- context['highlighted_source'] = highlight_code(submission.source.source, submission.language.pygments)
+ context['highlighted_source'] = highlight_code(
+ submission.source.source, submission.language.pygments
+ )
return context
@@ -122,7 +190,10 @@ def make_batch(batch, cases):
def get_statuses(batch, cases):
- cases = [TestCase(id=case.id, status=case.status, batch=batch, num_combined=1) for case in cases]
+ cases = [
+ TestCase(id=case.id, status=case.status, batch=batch, num_combined=1)
+ for case in cases
+ ]
if batch:
# Get the first non-AC case if it exists.
return [next((case for case in cases if case.status != 'AC'), cases[0])]
@@ -134,14 +205,22 @@ def combine_statuses(status_cases, submission):
ret = []
# If the submission is not graded and the final case is a batch,
# we don't actually know if it is completed or not, so just remove it.
- if not submission.is_graded and len(status_cases) > 0 and status_cases[-1].batch is not None:
+ if (
+ not submission.is_graded
+ and len(status_cases) > 0
+ and status_cases[-1].batch is not None
+ ):
status_cases.pop()
for key, group in groupby(status_cases, key=attrgetter('status')):
group = list(group)
if len(group) > 10:
# Grab the first case's id so the user can jump to that case, and combine the rest.
- ret.append(TestCase(id=group[0].id, status=key, batch=None, num_combined=len(group)))
+ ret.append(
+ TestCase(
+ id=group[0].id, status=key, batch=None, num_combined=len(group)
+ )
+ )
else:
ret.extend(group)
return ret
@@ -176,12 +255,16 @@ def get_context_data(self, **kwargs):
submission = self.object
context['last_msg'] = event.last()
- context['batches'], statuses, context['max_execution_time'] = group_test_cases(submission.test_cases.all())
+ context['batches'], statuses, context['max_execution_time'] = group_test_cases(
+ submission.test_cases.all()
+ )
context['statuses'] = combine_statuses(statuses, submission)
context['time_limit'] = submission.problem.time_limit
try:
- lang_limit = submission.problem.language_limits.get(language=submission.language)
+ lang_limit = submission.problem.language_limits.get(
+ language=submission.language
+ )
except ObjectDoesNotExist:
pass
else:
@@ -195,7 +278,9 @@ class SubmissionTestCaseQuery(SubmissionStatus):
def get(self, request, *args, **kwargs):
if 'id' not in request.GET or not request.GET['id'].isdigit():
return HttpResponseBadRequest()
- self.kwargs[self.pk_url_kwarg] = kwargs[self.pk_url_kwarg] = int(request.GET['id'])
+ self.kwargs[self.pk_url_kwarg] = kwargs[self.pk_url_kwarg] = int(
+ request.GET['id']
+ )
return super(SubmissionTestCaseQuery, self).get(request, *args, **kwargs)
@@ -208,8 +293,9 @@ def get(self, request, *args, **kwargs):
@require_POST
def abort_submission(request, submission):
submission = get_object_or_404(Submission, id=int(submission))
- if (not request.user.has_perm('judge.abort_any_submission') and
- (submission.rejudged_date is not None or request.profile != submission.user)):
+ if not request.user.has_perm('judge.abort_any_submission') and (
+ submission.rejudged_date is not None or request.profile != submission.user
+ ):
raise PermissionDenied()
submission.abort()
return HttpResponseRedirect(reverse('submission_status', args=(submission.id,)))
@@ -253,7 +339,10 @@ def access_check(self, request):
@cached_property
def in_contest(self):
- return self.request.user.is_authenticated and self.request.profile.current_contest is not None
+ return (
+ self.request.user.is_authenticated
+ and self.request.profile.current_contest is not None
+ )
@cached_property
def contest(self):
@@ -264,34 +353,45 @@ def _get_queryset(self):
use_straight_join(queryset)
queryset = submission_related(queryset.order_by('-id'))
if self.show_problem:
- queryset = queryset.prefetch_related(Prefetch('problem__translations',
- queryset=ProblemTranslation.objects.filter(
- language=self.request.LANGUAGE_CODE), to_attr='_trans'))
+ queryset = queryset.prefetch_related(
+ Prefetch(
+ 'problem__translations',
+ queryset=ProblemTranslation.objects.filter(
+ language=self.request.LANGUAGE_CODE
+ ),
+ to_attr='_trans',
+ )
+ )
if self.in_contest:
queryset = queryset.filter(contest_object=self.contest)
if not self.contest.can_see_full_scoreboard(self.request.user):
queryset = queryset.filter(user=self.request.profile)
else:
- queryset = queryset.select_related('contest_object').defer('contest_object__description')
+ queryset = queryset.select_related('contest_object').defer(
+ 'contest_object__description'
+ )
if not self.request.user.has_perm('judge.see_private_contest'):
# Show submissions for any contest you can edit or where you can see submissions
contest_queryset = Contest.objects.filter(
- Q(authors=self.request.profile) |
- Q(curators=self.request.profile) |
- Q(tester_see_submissions=True, testers=self.request.profile) |
- Q(view_contest_submissions=self.request.profile) |
- Q(scoreboard_visibility=Contest.SCOREBOARD_VISIBLE) |
- Q(end_time__lt=timezone.now(), scoreboard_visibility__in=(
- Contest.SCOREBOARD_AFTER_PARTICIPATION,
- Contest.SCOREBOARD_AFTER_CONTEST,
- )),
+ Q(authors=self.request.profile)
+ | Q(curators=self.request.profile)
+ | Q(tester_see_submissions=True, testers=self.request.profile)
+ | Q(view_contest_submissions=self.request.profile)
+ | Q(scoreboard_visibility=Contest.SCOREBOARD_VISIBLE)
+ | Q(
+ end_time__lt=timezone.now(),
+ scoreboard_visibility__in=(
+ Contest.SCOREBOARD_AFTER_PARTICIPATION,
+ Contest.SCOREBOARD_AFTER_CONTEST,
+ ),
+ ),
).distinct()
queryset = queryset.filter(
- Q(user=self.request.profile) |
- Q(contest_object__in=contest_queryset) |
- Q(contest_object__isnull=True),
+ Q(user=self.request.profile)
+ | Q(contest_object__in=contest_queryset)
+ | Q(contest_object__isnull=True),
)
if self.selected_languages:
@@ -299,8 +399,13 @@ def _get_queryset(self):
# so we are forcing an eager evaluation to get the IDs right here.
# Otherwise, with multiple language filters, MariaDB refuses to use an index
# (or runs the subquery for every submission, which is even more horrifying to think about).
- queryset = queryset.filter(language__in=list(
- Language.objects.filter(key__in=self.selected_languages).values_list('id', flat=True)))
+ queryset = queryset.filter(
+ language__in=list(
+ Language.objects.filter(
+ key__in=self.selected_languages
+ ).values_list('id', flat=True)
+ )
+ )
if self.selected_statuses:
queryset = queryset.filter(result__in=self.selected_statuses)
@@ -323,7 +428,9 @@ def get_searchable_status_codes(self):
hidden_codes = ['SC']
if not self.request.user.is_superuser and not self.request.user.is_staff:
hidden_codes += ['IE']
- return [(key, value) for key, value in Submission.RESULT if key not in hidden_codes]
+ return [
+ (key, value) for key, value in Submission.RESULT if key not in hidden_codes
+ ]
def get_context_data(self, **kwargs):
context = super(SubmissionsListBase, self).get_context_data(**kwargs)
@@ -333,9 +440,15 @@ def get_context_data(self, **kwargs):
context['show_problem'] = self.show_problem
profile = self.request.profile
- context['completed_problem_ids'] = memo_lazy(lambda: user_completed_ids(profile), set) if authenticated else []
- context['editable_problem_ids'] = memo_lazy(lambda: user_editable_ids(profile), set) if authenticated else []
- context['tester_problem_ids'] = memo_lazy(lambda: user_tester_ids(profile), set) if authenticated else []
+ context['completed_problem_ids'] = (
+ memo_lazy(lambda: user_completed_ids(profile), set) if authenticated else []
+ )
+ context['editable_problem_ids'] = (
+ memo_lazy(lambda: user_editable_ids(profile), set) if authenticated else []
+ )
+ context['tester_problem_ids'] = (
+ memo_lazy(lambda: user_tester_ids(profile), set) if authenticated else []
+ )
context['all_languages'] = Language.objects.all().values_list('key', 'name')
context['selected_languages'] = self.selected_languages
@@ -344,9 +457,13 @@ def get_context_data(self, **kwargs):
context['selected_statuses'] = self.selected_statuses
context['results_json'] = mark_safe(json.dumps(self.get_result_data()))
- context['results_colors_json'] = mark_safe(json.dumps(settings.DMOJ_STATS_SUBMISSION_RESULT_COLORS))
+ context['results_colors_json'] = mark_safe(
+ json.dumps(settings.DMOJ_STATS_SUBMISSION_RESULT_COLORS)
+ )
- context['page_suffix'] = suffix = ('?' + self.request.GET.urlencode()) if self.request.GET else ''
+ context['page_suffix'] = suffix = (
+ ('?' + self.request.GET.urlencode()) if self.request.GET else ''
+ )
context['first_page_href'] = (self.first_page_href or '.') + suffix
context['my_submissions_link'] = self.get_my_submissions_page()
context['all_submissions_link'] = self.get_all_submissions_page()
@@ -379,7 +496,9 @@ def get(self, request, *args, **kwargs):
class ConditionalUserTabMixin(object):
@cached_property
def is_own(self):
- return self.request.user.is_authenticated and self.request.profile == self.profile
+ return (
+ self.request.user.is_authenticated and self.request.profile == self.profile
+ )
def get_context_data(self, **kwargs):
context = super(ConditionalUserTabMixin, self).get_context_data(**kwargs)
@@ -393,7 +512,11 @@ def get_context_data(self, **kwargs):
class AllUserSubmissions(ConditionalUserTabMixin, UserMixin, SubmissionsListBase):
def get_queryset(self):
- return super(AllUserSubmissions, self).get_queryset().filter(user_id=self.profile.id)
+ return (
+ super(AllUserSubmissions, self)
+ .get_queryset()
+ .filter(user_id=self.profile.id)
+ )
def get_title(self):
if self.is_own:
@@ -403,14 +526,22 @@ def get_title(self):
def get_content_title(self):
if self.is_own:
return _('All my submissions')
- return mark_safe(escape(_('All submissions by %s')) % (
- format_html('{0} ', self.profile.display_name,
- reverse('user_page', args=[self.username])),
- ))
+ return mark_safe(
+ escape(_('All submissions by %s'))
+ % (
+ format_html(
+ '{0} ',
+ self.profile.display_name,
+ 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})
+ 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)
@@ -426,18 +557,33 @@ class ProblemSubmissionsBase(SubmissionsListBase):
check_contest_in_access_check = True
def get_queryset(self):
- if self.in_contest and not self.contest.contest_problems.filter(problem_id=self.problem.id).exists():
+ if (
+ self.in_contest
+ and not self.contest.contest_problems.filter(
+ problem_id=self.problem.id
+ ).exists()
+ ):
raise Http404()
- return super(ProblemSubmissionsBase, self)._get_queryset().filter(problem_id=self.problem.id)
+ return (
+ super(ProblemSubmissionsBase, self)
+ ._get_queryset()
+ .filter(problem_id=self.problem.id)
+ )
def get_title(self):
return _('All submissions for %s') % self.problem_name
def get_content_title(self):
- return mark_safe(escape(_('All submissions for %s')) % (
- format_html('{0} ', self.problem_name,
- reverse('problem_detail', args=[self.problem.code])),
- ))
+ return mark_safe(
+ escape(_('All submissions for %s'))
+ % (
+ format_html(
+ '{0} ',
+ self.problem_name,
+ reverse('problem_detail', args=[self.problem.code]),
+ ),
+ )
+ )
def access_check_contest(self, request):
if self.in_contest and not self.contest.can_see_own_scoreboard(request.user):
@@ -445,7 +591,11 @@ def access_check_contest(self, request):
def access_check(self, request):
# FIXME: This should be rolled into the `is_accessible_by` check when implementing #1509
- if self.in_contest and request.user.is_authenticated and request.profile.id in self.contest.editor_ids:
+ if (
+ self.in_contest
+ and request.user.is_authenticated
+ and request.profile.id in self.contest.editor_ids
+ ):
return
if not self.problem.is_accessible_by(request.user):
@@ -462,7 +612,9 @@ def get(self, request, *args, **kwargs):
return super(ProblemSubmissionsBase, self).get(request, *args, **kwargs)
def get_all_submissions_page(self):
- return reverse('chronological_submissions', kwargs={'problem': self.problem.code})
+ return reverse(
+ 'chronological_submissions', kwargs={'problem': self.problem.code}
+ )
def get_context_data(self, **kwargs):
context = super(ProblemSubmissionsBase, self).get_context_data(**kwargs)
@@ -470,15 +622,22 @@ def get_context_data(self, **kwargs):
context['dynamic_update'] = context['page_obj'].number == 1
context['dynamic_problem_id'] = self.problem.id
context['last_msg'] = event.last()
- context['best_submissions_link'] = reverse('ranked_submissions', kwargs={'problem': self.problem.code})
+ context['best_submissions_link'] = reverse(
+ 'ranked_submissions', kwargs={'problem': self.problem.code}
+ )
return context
class ProblemSubmissions(ProblemSubmissionsBase):
def get_my_submissions_page(self):
if self.request.user.is_authenticated:
- return reverse('user_submissions', kwargs={'problem': self.problem.code,
- 'user': self.request.user.username})
+ return reverse(
+ 'user_submissions',
+ kwargs={
+ 'problem': self.problem.code,
+ 'user': self.request.user.username,
+ },
+ )
class UserProblemSubmissions(ConditionalUserTabMixin, UserMixin, ProblemSubmissions):
@@ -491,27 +650,47 @@ def access_check(self, request):
self.access_check_contest(request)
def get_queryset(self):
- return super(UserProblemSubmissions, self).get_queryset().filter(user_id=self.profile.id)
+ return (
+ super(UserProblemSubmissions, self)
+ .get_queryset()
+ .filter(user_id=self.profile.id)
+ )
def get_title(self):
if self.is_own:
return _('My submissions for %(problem)s') % {'problem': self.problem_name}
return _("%(user)s's submissions for %(problem)s") % {
- 'user': self.profile.display_name, 'problem': self.problem_name,
+ 'user': self.profile.display_name,
+ 'problem': self.problem_name,
}
def get_content_title(self):
if self.request.user.is_authenticated and self.request.profile == self.profile:
- return mark_safe(escape(_('My submissions for %(problem)s')) % {
- 'problem': format_html('{0} ', self.problem_name,
- reverse('problem_detail', args=[self.problem.code])),
- })
- return mark_safe(escape(_("%(user)s's submissions for %(problem)s")) % {
- 'user': format_html('{0} ', self.profile.display_name,
- reverse('user_page', args=[self.username])),
- 'problem': format_html('{0} ', self.problem_name,
- reverse('problem_detail', args=[self.problem.code])),
- })
+ return mark_safe(
+ escape(_('My submissions for %(problem)s'))
+ % {
+ 'problem': format_html(
+ '{0} ',
+ self.problem_name,
+ reverse('problem_detail', args=[self.problem.code]),
+ ),
+ }
+ )
+ return mark_safe(
+ escape(_("%(user)s's submissions for %(problem)s"))
+ % {
+ 'user': format_html(
+ '{0} ',
+ self.profile.display_name,
+ reverse('user_page', args=[self.username]),
+ ),
+ 'problem': format_html(
+ '{0} ',
+ self.problem_name,
+ reverse('problem_detail', args=[self.problem.code]),
+ ),
+ }
+ )
def get_context_data(self, **kwargs):
context = super(UserProblemSubmissions, self).get_context_data(**kwargs)
@@ -529,19 +708,32 @@ def single_submission(request):
return HttpResponseBadRequest()
authenticated = request.user.is_authenticated
- submission = get_object_or_404(submission_related(Submission.objects.all()), id=int(request.GET['id']))
+ submission = get_object_or_404(
+ submission_related(Submission.objects.all()), id=int(request.GET['id'])
+ )
if not submission.problem.is_accessible_by(request.user):
raise Http404()
- return render(request, 'submission/row.html', {
- 'submission': submission,
- '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),
- 'profile_id': request.profile.id if authenticated else 0,
- })
+ return render(
+ request,
+ 'submission/row.html',
+ {
+ 'submission': submission,
+ '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),
+ 'profile_id': request.profile.id if authenticated else 0,
+ },
+ )
class AllSubmissions(InfinitePaginationMixin, SubmissionsListBase):
@@ -553,7 +745,9 @@ def use_infinite_pagination(self):
def get_my_submissions_page(self):
if self.request.user.is_authenticated:
- return reverse('all_user_submissions', kwargs={'user': self.request.user.username})
+ return reverse(
+ 'all_user_submissions', kwargs={'user': self.request.user.username}
+ )
def get_context_data(self, **kwargs):
context = super(AllSubmissions, self).get_context_data(**kwargs)
@@ -563,7 +757,12 @@ def get_context_data(self, **kwargs):
return context
def _get_result_data(self, queryset=None):
- if queryset is not None or self.in_contest or self.selected_languages or self.selected_statuses:
+ if (
+ queryset is not None
+ or self.in_contest
+ or self.selected_languages
+ or self.selected_statuses
+ ):
return super(AllSubmissions, self)._get_result_data(queryset)
key = 'global_submission_result_data'
@@ -590,11 +789,18 @@ def access_check(self, request):
if not request.user.has_perm('judge.see_private_contest'):
if not self.contest.is_visible:
raise Http404()
- if self.contest.start_time is not None and self.contest.start_time > timezone.now():
+ if (
+ self.contest.start_time is not None
+ and self.contest.start_time > timezone.now()
+ ):
raise Http404()
def get_problem_number(self, problem):
- return self.contest.contest_problems.select_related('problem').get(problem=problem).order
+ return (
+ self.contest.contest_problems.select_related('problem')
+ .get(problem=problem)
+ .order
+ )
def get(self, request, *args, **kwargs):
if 'contest' not in kwargs:
@@ -616,26 +822,46 @@ def access_check(self, request):
super().access_check(request)
if not self.contest.users.filter(user_id=self.profile.id).exists():
raise Http404()
- if not self.is_own and not self.contest.can_see_full_scoreboard(self.request.user):
+ if not self.is_own and not self.contest.can_see_full_scoreboard(
+ self.request.user
+ ):
raise Http404()
def get_content_title(self):
if self.is_own:
- return mark_safe(escape(_('My submissions in %(contest)s')) % {
- 'contest': format_html('{0} ', self.contest.name,
- reverse('contest_view', args=[self.contest.key])),
- })
- return mark_safe(escape(_("%(user)s's submissions in %(contest)s")) % {
- 'user': format_html('{0} ', self.profile.display_name,
- reverse('user_page', args=[self.username])),
- 'contest': format_html('{0} ', self.contest.name,
- reverse('contest_view', args=[self.contest.key])),
- })
+ return mark_safe(
+ escape(_('My submissions in %(contest)s'))
+ % {
+ 'contest': format_html(
+ '{0} ',
+ self.contest.name,
+ reverse('contest_view', args=[self.contest.key]),
+ ),
+ }
+ )
+ return mark_safe(
+ escape(_("%(user)s's submissions in %(contest)s"))
+ % {
+ 'user': format_html(
+ '{0} ',
+ self.profile.display_name,
+ reverse('user_page', args=[self.username]),
+ ),
+ 'contest': format_html(
+ '{0} ',
+ self.contest.name,
+ reverse('contest_view', args=[self.contest.key]),
+ ),
+ }
+ )
def get_queryset(self):
queryset = super().get_queryset()
# FIXME: fix this line of code when #1509 is implemented
- if not self.request.user.is_authenticated or self.request.profile.id not in self.contest.editor_ids:
+ if (
+ not self.request.user.is_authenticated
+ or self.request.profile.id not in self.contest.editor_ids
+ ):
filter_submissions_by_visible_problems(queryset, self.request.user)
return queryset
@@ -661,18 +887,37 @@ def access_check(self, request):
def get_content_title(self):
if self.problem.is_accessible_by(self.request.user):
- return mark_safe(escape(_("{user}'s submissions for {problem} in {contest}")).format(
- user=format_html('{0} ', self.profile.display_name,
- reverse('user_page', args=[self.username])),
- problem=format_html('{0} ', self.problem_name,
- reverse('problem_detail', args=[self.problem.code])),
- contest=format_html('{0} ', self.contest.name,
- reverse('contest_view', args=[self.contest.key])),
- ))
- return mark_safe(escape(_("{user}'s submissions for problem {number} in {contest}")).format(
- user=format_html('{0} ', self.profile.display_name,
- reverse('user_page', args=[self.username])),
- number=self.get_problem_number(self.problem),
- contest=format_html('{0} ', self.contest.name,
- reverse('contest_view', args=[self.contest.key])),
- ))
+ return mark_safe(
+ escape(_("{user}'s submissions for {problem} in {contest}")).format(
+ user=format_html(
+ '{0} ',
+ self.profile.display_name,
+ reverse('user_page', args=[self.username]),
+ ),
+ problem=format_html(
+ '{0} ',
+ self.problem_name,
+ reverse('problem_detail', args=[self.problem.code]),
+ ),
+ contest=format_html(
+ '{0} ',
+ self.contest.name,
+ reverse('contest_view', args=[self.contest.key]),
+ ),
+ )
+ )
+ return mark_safe(
+ escape(_("{user}'s submissions for problem {number} in {contest}")).format(
+ user=format_html(
+ '{0} ',
+ self.profile.display_name,
+ reverse('user_page', args=[self.username]),
+ ),
+ number=self.get_problem_number(self.problem),
+ contest=format_html(
+ '{0} ',
+ self.contest.name,
+ reverse('contest_view', args=[self.contest.key]),
+ ),
+ )
+ )
diff --git a/judge/views/tasks.py b/judge/views/tasks.py
index 42e123fbc3..61a804294f 100644
--- a/judge/views/tasks.py
+++ b/judge/views/tasks.py
@@ -4,7 +4,12 @@
from celery.result import AsyncResult
from django.core.exceptions import PermissionDenied
-from django.http import Http404, HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
+from django.http import (
+ Http404,
+ HttpResponseBadRequest,
+ HttpResponseRedirect,
+ JsonResponse,
+)
from django.shortcuts import render
from django.urls import reverse
from django.utils.http import is_safe_url
@@ -18,7 +23,12 @@ def get_task_status(task_id):
result = AsyncResult(task_id)
info = result.result
if result.state == 'PROGRESS':
- return {'code': 'PROGRESS', 'done': info['done'], 'total': info['total'], 'stage': info['stage']}
+ return {
+ 'code': 'PROGRESS',
+ 'done': info['done'],
+ 'total': info['total'],
+ 'stage': info['stage'],
+ }
elif result.state == 'SUCCESS':
return {'code': 'SUCCESS'}
elif result.state == 'FAILURE':
@@ -41,16 +51,24 @@ def task_status(request, task_id):
if status['code'] == 'SUCCESS' and redirect:
return HttpResponseRedirect(redirect)
- return render(request, 'task_status.html', {
- 'task_id': task_id, 'task_status': json.dumps(status),
- 'message': request.GET.get('message', ''), 'redirect': redirect or '',
- })
+ return render(
+ request,
+ 'task_status.html',
+ {
+ 'task_id': task_id,
+ 'task_status': json.dumps(status),
+ 'message': request.GET.get('message', ''),
+ 'redirect': redirect or '',
+ },
+ )
@short_circuit_middleware
def task_status_ajax(request):
if 'id' not in request.GET:
- return HttpResponseBadRequest('Need to pass GET parameter "id"', content_type='text/plain')
+ return HttpResponseBadRequest(
+ 'Need to pass GET parameter "id"', content_type='text/plain'
+ )
return JsonResponse(get_task_status(request.GET['id']))
@@ -61,6 +79,12 @@ def demo_task(request, task, message):
return redirect_to_task_status(result, message=message, redirect=reverse('home'))
-demo_success = partial(demo_task, task=success, message='Running example task that succeeds...')
-demo_failure = partial(demo_task, task=failure, message='Running example task that fails...')
-demo_progress = partial(demo_task, task=progress, message='Running example task that waits 10 seconds...')
+demo_success = partial(
+ demo_task, task=success, message='Running example task that succeeds...'
+)
+demo_failure = partial(
+ demo_task, task=failure, message='Running example task that fails...'
+)
+demo_progress = partial(
+ demo_task, task=progress, message='Running example task that waits 10 seconds...'
+)
diff --git a/judge/views/ticket.py b/judge/views/ticket.py
index eec3d45b43..cd7dc87d0e 100644
--- a/judge/views/ticket.py
+++ b/judge/views/ticket.py
@@ -2,8 +2,18 @@
from django import forms
from django.contrib.auth.mixins import LoginRequiredMixin
-from django.core.exceptions import ImproperlyConfigured, PermissionDenied, ValidationError
-from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
+from django.core.exceptions import (
+ ImproperlyConfigured,
+ PermissionDenied,
+ ValidationError,
+)
+from django.http import (
+ Http404,
+ HttpResponse,
+ HttpResponseBadRequest,
+ HttpResponseRedirect,
+ JsonResponse,
+)
from django.shortcuts import get_object_or_404
from django.template.defaultfilters import truncatechars
from django.template.loader import get_template
@@ -24,9 +34,15 @@
from judge.views.problem import ProblemMixin
from judge.widgets import HeavyPreviewPageDownWidget
-ticket_widget = (forms.Textarea() if HeavyPreviewPageDownWidget is None else
- HeavyPreviewPageDownWidget(preview=reverse_lazy('ticket_preview'),
- preview_timeout=1000, hide_preview_button=True))
+ticket_widget = (
+ forms.Textarea()
+ if HeavyPreviewPageDownWidget is None
+ else HeavyPreviewPageDownWidget(
+ preview=reverse_lazy('ticket_preview'),
+ preview_timeout=1000,
+ hide_preview_button=True,
+ )
+)
class TicketForm(forms.Form):
@@ -45,7 +61,11 @@ def clean(self):
if profile.mute:
raise ValidationError(_('Your part is silent, little toad.'))
if not self.request.in_contest and not profile.has_any_solves:
- raise ValidationError(_('You must solve at least one problem before you can create a ticket.'))
+ raise ValidationError(
+ _(
+ 'You must solve at least one problem before you can create a ticket.'
+ )
+ )
return super(TicketForm, self).clean()
@@ -65,15 +85,22 @@ def form_valid(self, form):
ticket = Ticket(user=self.request.profile, title=form.cleaned_data['title'])
ticket.linked_item = self.object
ticket.save()
- message = TicketMessage(ticket=ticket, user=ticket.user, body=form.cleaned_data['body'])
+ message = TicketMessage(
+ ticket=ticket, user=ticket.user, body=form.cleaned_data['body']
+ )
message.save()
ticket.assignees.set(self.get_assignees())
if event.real:
- event.post('tickets', {
- 'type': 'new-ticket', 'id': ticket.id,
- 'message': message.id, 'user': ticket.user_id,
- 'assignees': list(ticket.assignees.values_list('id', flat=True)),
- })
+ event.post(
+ 'tickets',
+ {
+ 'type': 'new-ticket',
+ 'id': ticket.id,
+ 'message': message.id,
+ 'user': ticket.user_id,
+ 'assignees': list(ticket.assignees.values_list('id', flat=True)),
+ },
+ )
return HttpResponseRedirect(reverse('ticket', args=[ticket.id]))
@@ -91,9 +118,14 @@ def get_title(self):
return _('New ticket for %s') % self.object.name
def get_content_title(self):
- return mark_safe(escape(_('New ticket for %s')) %
- format_html('{1} ', reverse('problem_detail', args=[self.object.code]),
- self.object.translated_name(self.request.LANGUAGE_CODE)))
+ return mark_safe(
+ escape(_('New ticket for %s'))
+ % format_html(
+ '{1} ',
+ reverse('problem_detail', args=[self.object.code]),
+ self.object.translated_name(self.request.LANGUAGE_CODE),
+ )
+ )
def form_valid(self, form):
if not self.object.is_accessible_by(self.request.user):
@@ -129,23 +161,41 @@ class TicketView(TitleMixin, TicketMixin, SingleObjectFormView):
context_object_name = 'ticket'
def form_valid(self, form):
- message = TicketMessage(user=self.request.profile,
- body=form.cleaned_data['body'],
- ticket=self.object)
+ message = TicketMessage(
+ user=self.request.profile,
+ body=form.cleaned_data['body'],
+ ticket=self.object,
+ )
message.save()
if event.real:
- event.post('tickets', {
- 'type': 'ticket-message', 'id': self.object.id,
- 'message': message.id, 'user': self.object.user_id,
- 'assignees': list(self.object.assignees.values_list('id', flat=True)),
- })
- event.post('ticket-%d' % self.object.id, {
- 'type': 'ticket-message', 'message': message.id,
- })
- return HttpResponseRedirect('%s#message-%d' % (reverse('ticket', args=[self.object.id]), message.id))
+ event.post(
+ 'tickets',
+ {
+ 'type': 'ticket-message',
+ 'id': self.object.id,
+ 'message': message.id,
+ 'user': self.object.user_id,
+ 'assignees': list(
+ self.object.assignees.values_list('id', flat=True)
+ ),
+ },
+ )
+ event.post(
+ 'ticket-%d' % self.object.id,
+ {
+ 'type': 'ticket-message',
+ 'message': message.id,
+ },
+ )
+ return HttpResponseRedirect(
+ '%s#message-%d' % (reverse('ticket', args=[self.object.id]), message.id)
+ )
def get_title(self):
- return _('%(title)s - Ticket %(id)d') % {'title': self.object.title, 'id': self.object.id}
+ return _('%(title)s - Ticket %(id)d') % {
+ 'title': self.object.title,
+ 'id': self.object.id,
+ }
def get_context_data(self, **kwargs):
context = super(TicketView, self).get_context_data(**kwargs)
@@ -166,15 +216,26 @@ def post(self, request, *args, **kwargs):
ticket.is_open = self.open
ticket.save()
if event.real:
- event.post('tickets', {
- 'type': 'ticket-status', 'id': ticket.id,
- 'open': self.open, 'user': ticket.user_id,
- 'assignees': list(ticket.assignees.values_list('id', flat=True)),
- 'title': ticket.title,
- })
- event.post('ticket-%d' % ticket.id, {
- 'type': 'ticket-status', 'open': self.open,
- })
+ event.post(
+ 'tickets',
+ {
+ 'type': 'ticket-status',
+ 'id': ticket.id,
+ 'open': self.open,
+ 'user': ticket.user_id,
+ 'assignees': list(
+ ticket.assignees.values_list('id', flat=True)
+ ),
+ 'title': ticket.title,
+ },
+ )
+ event.post(
+ 'ticket-%d' % ticket.id,
+ {
+ 'type': 'ticket-status',
+ 'open': self.open,
+ },
+ )
return HttpResponse(status=204)
@@ -232,7 +293,11 @@ def GET_with_session(self, key):
return self.request.GET.get(key, None) == '1'
def _get_queryset(self):
- return Ticket.objects.select_related('user__user').prefetch_related('assignees__user').order_by('-id')
+ return (
+ Ticket.objects.select_related('user__user')
+ .prefetch_related('assignees__user')
+ .order_by('-id')
+ )
def get_queryset(self):
queryset = self._get_queryset()
@@ -243,7 +308,9 @@ def get_queryset(self):
elif not self.can_edit_all:
queryset = filter_visible_tickets(queryset, self.request.user)
if self.filter_assignees:
- queryset = queryset.filter(assignees__user__username__in=self.filter_assignees)
+ queryset = queryset.filter(
+ assignees__user__username__in=self.filter_assignees
+ )
if self.filter_users:
queryset = queryset.filter(user__user__username__in=self.filter_users)
return queryset.distinct()
@@ -262,10 +329,20 @@ def get_context_data(self, **kwargs):
'own': self.GET_with_session('own'),
'user': self.filter_users,
'assignee': self.filter_assignees,
- 'user_id': json.dumps(list(Profile.objects.filter(user__username__in=self.filter_users)
- .values_list('id', flat=True))),
- 'assignee_id': json.dumps(list(Profile.objects.filter(user__username__in=self.filter_assignees)
- .values_list('id', flat=True))),
+ 'user_id': json.dumps(
+ list(
+ Profile.objects.filter(
+ user__username__in=self.filter_users
+ ).values_list('id', flat=True)
+ )
+ ),
+ 'assignee_id': json.dumps(
+ list(
+ Profile.objects.filter(
+ user__username__in=self.filter_assignees
+ ).values_list('id', flat=True)
+ )
+ ),
'own_id': self.profile.id if self.GET_with_session('own') else 'null',
}
context['last_msg'] = event.last()
@@ -289,7 +366,9 @@ def _get_queryset(self):
if problem.is_editable_by(self.request.user):
return problem.tickets.order_by('-id')
elif problem.is_accessible_by(self.request.user):
- return problem.tickets.filter(own_ticket_filter(self.profile.id)).order_by('-id')
+ return problem.tickets.filter(own_ticket_filter(self.profile.id)).order_by(
+ '-id'
+ )
raise Http404()
@@ -301,16 +380,32 @@ def get(self, request, *args, **kwargs):
return HttpResponseBadRequest()
ticket = self.get_object()
message = ticket.messages.first()
- return JsonResponse({
- 'row': get_template('ticket/row.html').render({'ticket': ticket}, request),
- 'notification': {
- 'title': _('New Ticket: %s') % ticket.title,
- 'body': '%s\n%s' % (_('#%(id)d, assigned to: %(users)s') % {
- 'id': ticket.id,
- 'users': (_(', ').join(ticket.assignees.values_list('user__username', flat=True)) or _('no one')),
- }, truncatechars(message.body, 200)),
- },
- })
+ return JsonResponse(
+ {
+ 'row': get_template('ticket/row.html').render(
+ {'ticket': ticket}, request
+ ),
+ 'notification': {
+ 'title': _('New Ticket: %s') % ticket.title,
+ 'body': '%s\n%s'
+ % (
+ _('#%(id)d, assigned to: %(users)s')
+ % {
+ 'id': ticket.id,
+ 'users': (
+ _(', ').join(
+ ticket.assignees.values_list(
+ 'user__username', flat=True
+ )
+ )
+ or _('no one')
+ ),
+ },
+ truncatechars(message.body, 200),
+ ),
+ },
+ }
+ )
class TicketMessageDataAjax(TicketMixin, SingleObjectMixin, View):
@@ -324,10 +419,14 @@ def get(self, request, *args, **kwargs):
message = ticket.messages.get(id=message_id)
except TicketMessage.DoesNotExist:
return HttpResponseBadRequest()
- return JsonResponse({
- 'message': get_template('ticket/message.html').render({'message': message, 'ticket': ticket}, request),
- 'notification': {
- 'title': _('New Ticket Message For: %s') % ticket.title,
- 'body': truncatechars(message.body, 200),
- },
- })
+ return JsonResponse(
+ {
+ 'message': get_template('ticket/message.html').render(
+ {'message': message, 'ticket': ticket}, request
+ ),
+ 'notification': {
+ 'title': _('New Ticket Message For: %s') % ticket.title,
+ 'body': truncatechars(message.body, 200),
+ },
+ }
+ )
diff --git a/judge/views/two_factor.py b/judge/views/two_factor.py
index 2e7dfdea73..863a44c2f0 100644
--- a/judge/views/two_factor.py
+++ b/judge/views/two_factor.py
@@ -9,7 +9,13 @@
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import SuccessURLAllowedHostsMixin
-from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
+from django.http import (
+ Http404,
+ HttpResponse,
+ HttpResponseBadRequest,
+ HttpResponseRedirect,
+ JsonResponse,
+)
from django.urls import reverse
from django.utils.http import is_safe_url
from django.utils.translation import gettext as _, gettext_lazy
@@ -76,8 +82,12 @@ def post(self, request, *args, **kwargs):
def get_context_data(self, **kwargs):
context = super(TOTPEnableView, self).get_context_data(**kwargs)
context['totp_key'] = self.request.session['totp_enable_key']
- context['scratch_codes'] = [] if self.is_edit else json.loads(self.profile.scratch_codes)
- context['qr_code'] = self.render_qr_code(self.request.user.username, context['totp_key'])
+ context['scratch_codes'] = (
+ [] if self.is_edit else json.loads(self.profile.scratch_codes)
+ )
+ context['qr_code'] = self.render_qr_code(
+ self.request.user.username, context['totp_key']
+ )
context['is_edit'] = self.is_edit
context['is_hardcore'] = settings.DMOJ_2FA_HARDCORE
return context
@@ -103,7 +113,9 @@ def render_qr_code(cls, username, key):
image = qr.make_image(fill_color='black', back_color='white')
buf = BytesIO()
image.save(buf, format='PNG')
- return 'data:image/png;base64,' + base64.b64encode(buf.getvalue()).decode('ascii')
+ return 'data:image/png;base64,' + base64.b64encode(buf.getvalue()).decode(
+ 'ascii'
+ )
class TOTPEditView(TOTPEnableView):
@@ -121,7 +133,11 @@ class TOTPDisableView(TOTPView):
def check_skip(self):
if not self.profile.is_totp_enabled:
return True
- return settings.DMOJ_REQUIRE_STAFF_2FA and self.request.user.is_staff and not self.profile.is_webauthn_enabled
+ return (
+ settings.DMOJ_REQUIRE_STAFF_2FA
+ and self.request.user.is_staff
+ and not self.profile.is_webauthn_enabled
+ )
def form_valid(self, form):
self.profile.is_totp_enabled = False
@@ -153,10 +169,13 @@ def get(self, request, *args, **kwargs):
icon_url=gravatar(request.user.email),
attestation='none',
).registration_dict
- data['excludeCredentials'] = [{
- 'type': 'public-key',
- 'id': {'_bytes': credential.cred_id},
- } for credential in request.profile.webauthn_credentials.all()]
+ data['excludeCredentials'] = [
+ {
+ 'type': 'public-key',
+ 'id': {'_bytes': credential.cred_id},
+ }
+ for credential in request.profile.webauthn_credentials.all()
+ ]
return JsonResponse(data, encoder=WebAuthnJSONEncoder)
def post(self, request, *args, **kwargs):
@@ -185,7 +204,8 @@ def post(self, request, *args, **kwargs):
return HttpResponseBadRequest(str(e))
model = WebAuthnCredential(
- user=request.profile, name=request.POST['name'],
+ user=request.profile,
+ name=request.POST['name'],
cred_id=credential.credential_id.decode('ascii'),
public_key=credential.public_key.decode('ascii'),
counter=credential.sign_count,
@@ -203,8 +223,12 @@ def get(self, request, *args, **kwargs):
challenge = os.urandom(32)
request.session['webauthn_assert'] = webauthn_encode(challenge)
data = webauthn.WebAuthnAssertionOptions(
- [credential.webauthn_user for credential in
- request.profile.webauthn_credentials.select_related('user__user')],
+ [
+ credential.webauthn_user
+ for credential in request.profile.webauthn_credentials.select_related(
+ 'user__user'
+ )
+ ],
challenge,
).assertion_dict
return JsonResponse(data, encoder=WebAuthnJSONEncoder)
@@ -218,8 +242,12 @@ def post(self, request, *args, **kwargs):
credential = self.get_object()
count = self.get_queryset().count()
- if settings.DMOJ_REQUIRE_STAFF_2FA and self.request.user.is_staff and \
- count <= 1 and not request.profile.is_totp_enabled:
+ if (
+ settings.DMOJ_REQUIRE_STAFF_2FA
+ and self.request.user.is_staff
+ and count <= 1
+ and not request.profile.is_totp_enabled
+ ):
return HttpResponseBadRequest(_('Staff may not disable 2FA'))
credential.delete()
@@ -239,8 +267,9 @@ def get_form_kwargs(self):
return result
def check_skip(self):
- return ((not self.profile.is_totp_enabled and not self.profile.is_webauthn_enabled) or
- self.request.session.get('2fa_passed', False))
+ return (
+ not self.profile.is_totp_enabled and not self.profile.is_webauthn_enabled
+ ) or self.request.session.get('2fa_passed', False)
def next_page(self):
redirect_to = self.request.GET.get('next', '')
@@ -249,7 +278,9 @@ def next_page(self):
allowed_hosts=self.get_success_url_allowed_hosts(),
require_https=self.request.is_secure(),
)
- return HttpResponseRedirect((redirect_to if url_is_safe else '') or reverse('user_page'))
+ return HttpResponseRedirect(
+ (redirect_to if url_is_safe else '') or reverse('user_page')
+ )
def form_valid(self, form):
self.request.session['2fa_passed'] = True
diff --git a/judge/views/user.py b/judge/views/user.py
index 19baad6d66..68dbecaa2b 100644
--- a/judge/views/user.py
+++ b/judge/views/user.py
@@ -11,7 +11,12 @@
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import Permission, User
-from django.contrib.auth.views import LoginView, PasswordChangeView, PasswordResetView, redirect_to_login
+from django.contrib.auth.views import (
+ LoginView,
+ PasswordChangeView,
+ PasswordResetView,
+ redirect_to_login,
+)
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.shortcuts import get_current_site
from django.core import signing
@@ -31,7 +36,13 @@
from django.views.generic import DetailView, FormView, ListView, TemplateView, View
from reversion import revisions
-from judge.forms import CustomAuthenticationForm, DownloadDataForm, EmailChangeForm, ProfileForm, newsletter_id
+from judge.forms import (
+ CustomAuthenticationForm,
+ DownloadDataForm,
+ EmailChangeForm,
+ ProfileForm,
+ newsletter_id,
+)
from judge.models import Profile, Submission
from judge.performance_points import get_pp_breakdown
from judge.ratings import rating_class, rating_progress
@@ -44,11 +55,24 @@
from judge.utils.ranker import ranker
from judge.utils.subscription import Subscription
from judge.utils.unicode import utf8text
-from judge.utils.views import DiggPaginatorMixin, QueryStringSortMixin, TitleMixin, add_file_response, generic_message
+from judge.utils.views import (
+ DiggPaginatorMixin,
+ QueryStringSortMixin,
+ TitleMixin,
+ add_file_response,
+ generic_message,
+)
from .contests import ContestRanking
-__all__ = ['UserPage', 'UserAboutPage', 'UserProblemsPage', 'UserDownloadData', 'UserPrepareData',
- 'users', 'edit_profile']
+__all__ = [
+ 'UserPage',
+ 'UserAboutPage',
+ 'UserProblemsPage',
+ 'UserDownloadData',
+ 'UserPrepareData',
+ 'users',
+ 'edit_profile',
+]
def remap_keys(iterable, mapping):
@@ -80,12 +104,18 @@ def dispatch(self, request, *args, **kwargs):
try:
return super(UserPage, self).dispatch(request, *args, **kwargs)
except Http404:
- return generic_message(request, _('No such user'), _('No user handle "%s".') %
- self.kwargs.get(self.slug_url_kwarg, None))
+ return generic_message(
+ request,
+ _('No such user'),
+ _('No user handle "%s".') % self.kwargs.get(self.slug_url_kwarg, None),
+ )
def get_title(self):
- return (_('My account') if self.request.user == self.object.user else
- _('User %s') % self.object.display_name)
+ return (
+ _('My account')
+ if self.request.user == self.object.user
+ else _('User %s') % self.object.display_name
+ )
# TODO: the same code exists in problem.py, maybe move to problems.py?
@cached_property
@@ -108,25 +138,49 @@ def get_context_data(self, **kwargs):
context = super(UserPage, self).get_context_data(**kwargs)
context['hide_solved'] = int(self.hide_solved)
- context['authored'] = self.object.authored_problems.filter(is_public=True, is_organization_private=False) \
- .select_related('group').order_by('code')
+ context['authored'] = (
+ self.object.authored_problems.filter(
+ is_public=True, is_organization_private=False
+ )
+ .select_related('group')
+ .order_by('code')
+ )
rating = self.object.ratings.order_by('-contest__end_time')[:1]
context['rating'] = rating[0] if rating else None
- context['rank'] = Profile.objects.filter(
- is_unlisted=False, performance_points__gt=self.object.performance_points,
- ).exclude(id=self.object.id).count() + 1
+ context['rank'] = (
+ Profile.objects.filter(
+ is_unlisted=False,
+ performance_points__gt=self.object.performance_points,
+ )
+ .exclude(id=self.object.id)
+ .count()
+ + 1
+ )
if rating:
- context['rating_rank'] = Profile.objects.filter(
- is_unlisted=False, rating__gt=self.object.rating,
- ).count() + 1
- context.update(self.object.ratings.aggregate(min_rating=Min('rating'), max_rating=Max('rating'),
- contests=Count('contest')))
+ context['rating_rank'] = (
+ Profile.objects.filter(
+ is_unlisted=False,
+ rating__gt=self.object.rating,
+ ).count()
+ + 1
+ )
+ context.update(
+ self.object.ratings.aggregate(
+ min_rating=Min('rating'),
+ max_rating=Max('rating'),
+ contests=Count('contest'),
+ )
+ )
return context
def get(self, request, *args, **kwargs):
- self.hide_solved = request.GET.get('hide_solved') == '1' if 'hide_solved' in request.GET else False
+ self.hide_solved = (
+ request.GET.get('hide_solved') == '1'
+ if 'hide_solved' in request.GET
+ else False
+ )
return super(UserPage, self).get(request, *args, **kwargs)
@@ -164,36 +218,63 @@ class UserAboutPage(UserPage):
def get_context_data(self, **kwargs):
context = super(UserAboutPage, self).get_context_data(**kwargs)
- ratings = context['ratings'] = self.object.ratings.order_by('-contest__end_time').select_related('contest') \
+ ratings = context['ratings'] = (
+ self.object.ratings.order_by('-contest__end_time')
+ .select_related('contest')
.defer('contest__description')
+ )
- context['rating_data'] = mark_safe(json.dumps([{
- 'label': rating.contest.name,
- 'rating': rating.rating,
- 'ranking': rating.rank,
- 'link': '%s#!%s' % (reverse('contest_ranking', args=(rating.contest.key,)), self.object.user.username),
- 'timestamp': (rating.contest.end_time - EPOCH).total_seconds() * 1000,
- 'date': date_format(timezone.localtime(rating.contest.end_time), _('M j, Y, G:i')),
- 'class': rating_class(rating.rating),
- 'height': '%.3fem' % rating_progress(rating.rating),
- } for rating in ratings]))
+ context['rating_data'] = mark_safe(
+ json.dumps(
+ [
+ {
+ 'label': rating.contest.name,
+ 'rating': rating.rating,
+ 'ranking': rating.rank,
+ 'link': '%s#!%s'
+ % (
+ reverse('contest_ranking', args=(rating.contest.key,)),
+ self.object.user.username,
+ ),
+ 'timestamp': (rating.contest.end_time - EPOCH).total_seconds()
+ * 1000,
+ 'date': date_format(
+ timezone.localtime(rating.contest.end_time),
+ _('M j, Y, G:i'),
+ ),
+ 'class': rating_class(rating.rating),
+ 'height': '%.3fem' % rating_progress(rating.rating),
+ }
+ for rating in ratings
+ ]
+ )
+ )
submissions = (
- self.object.submission_set
- .annotate(date_only=TruncDate('date'))
- .values('date_only').annotate(cnt=Count('id'))
+ self.object.submission_set.annotate(date_only=TruncDate('date'))
+ .values('date_only')
+ .annotate(cnt=Count('id'))
)
- context['submission_data'] = mark_safe(json.dumps({
- date_counts['date_only'].isoformat(): date_counts['cnt'] for date_counts in submissions
- }))
- context['submission_metadata'] = mark_safe(json.dumps({
- 'min_year': (
- self.object.submission_set
- .annotate(year_only=ExtractYear('date'))
- .aggregate(min_year=Min('year_only'))['min_year']
- ),
- }))
+ context['submission_data'] = mark_safe(
+ json.dumps(
+ {
+ date_counts['date_only'].isoformat(): date_counts['cnt']
+ for date_counts in submissions
+ }
+ )
+ )
+ context['submission_metadata'] = mark_safe(
+ json.dumps(
+ {
+ 'min_year': (
+ self.object.submission_set.annotate(
+ year_only=ExtractYear('date')
+ ).aggregate(min_year=Min('year_only'))['min_year']
+ ),
+ }
+ )
+ )
return context
@@ -203,11 +284,27 @@ class UserProblemsPage(UserPage):
def get_context_data(self, **kwargs):
context = super(UserProblemsPage, self).get_context_data(**kwargs)
- result = Submission.objects.filter(user=self.object, points__gt=0, problem__is_public=True,
- problem__is_organization_private=False) \
- .exclude(problem__in=self.get_completed_problems() if self.hide_solved else []) \
- .values('problem__id', 'problem__code', 'problem__name', 'problem__points', 'problem__group__full_name') \
- .distinct().annotate(points=Max('points')).order_by('problem__group__full_name', 'problem__code')
+ result = (
+ Submission.objects.filter(
+ user=self.object,
+ points__gt=0,
+ problem__is_public=True,
+ problem__is_organization_private=False,
+ )
+ .exclude(
+ problem__in=self.get_completed_problems() if self.hide_solved else []
+ )
+ .values(
+ 'problem__id',
+ 'problem__code',
+ 'problem__name',
+ 'problem__points',
+ 'problem__group__full_name',
+ )
+ .distinct()
+ .annotate(points=Max('points'))
+ .order_by('problem__group__full_name', 'problem__code')
+ )
def process_group(group, problems_iter):
problems = list(problems_iter)
@@ -215,11 +312,19 @@ def process_group(group, problems_iter):
return {'name': group, 'problems': problems, 'points': points}
context['best_submissions'] = [
- process_group(group, problems) for group, problems in itertools.groupby(
- remap_keys(result, {
- 'problem__code': 'code', 'problem__name': 'name', 'problem__points': 'total',
- 'problem__group__full_name': 'group',
- }), itemgetter('group'))
+ process_group(group, problems)
+ for group, problems in itertools.groupby(
+ remap_keys(
+ result,
+ {
+ 'problem__code': 'code',
+ 'problem__name': 'name',
+ 'problem__points': 'total',
+ 'problem__group__full_name': 'group',
+ },
+ ),
+ itemgetter('group'),
+ )
]
breakdown, has_more = get_pp_breakdown(self.object, start=0, end=10)
context['pp_breakdown'] = breakdown
@@ -248,16 +353,20 @@ def get(self, request, *args, **kwargs):
httpresp = super(UserPerformancePointsAjax, self).get(request, *args, **kwargs)
httpresp.render()
- return JsonResponse({
- 'results': utf8text(httpresp.content),
- 'has_more': self.has_more,
- })
+ return JsonResponse(
+ {
+ 'results': utf8text(httpresp.content),
+ 'has_more': self.has_more,
+ }
+ )
class UserDataMixin:
@cached_property
def data_path(self):
- return os.path.join(settings.DMOJ_USER_DATA_CACHE, '%s.zip' % self.request.profile.id)
+ return os.path.join(
+ settings.DMOJ_USER_DATA_CACHE, '%s.zip' % self.request.profile.id
+ )
def dispatch(self, request, *args, **kwargs):
if not settings.DMOJ_USER_DATA_DOWNLOAD or self.request.profile.mute:
@@ -276,9 +385,11 @@ def _now(self):
@cached_property
def can_prepare_data(self):
return (
- self.request.profile.data_last_downloaded is None or
- self.request.profile.data_last_downloaded + settings.DMOJ_USER_DATA_DOWNLOAD_RATELIMIT < self._now or
- not os.path.exists(self.data_path)
+ self.request.profile.data_last_downloaded is None
+ or self.request.profile.data_last_downloaded
+ + settings.DMOJ_USER_DATA_DOWNLOAD_RATELIMIT
+ < self._now
+ or not os.path.exists(self.data_path)
)
@cached_property
@@ -297,7 +408,9 @@ def in_progress_url(self):
def build_task_url(self, status_id):
return task_status_url_by_id(
- status_id, message=_('Preparing your data...'), redirect=reverse('user_prepare_data'),
+ status_id,
+ message=_('Preparing your data...'),
+ redirect=reverse('user_prepare_data'),
)
def get_title(self):
@@ -306,7 +419,9 @@ def get_title(self):
def form_valid(self, form):
self.request.profile.data_last_downloaded = self._now
self.request.profile.save()
- status = prepare_user_data.delay(self.request.profile.id, json.dumps(form.cleaned_data))
+ status = prepare_user_data.delay(
+ self.request.profile.id, json.dumps(form.cleaned_data)
+ )
cache.set(self.data_cache_key, status.id)
return HttpResponseRedirect(self.build_task_url(status.id))
@@ -318,8 +433,10 @@ def get_context_data(self, **kwargs):
context['ratelimit'] = settings.DMOJ_USER_DATA_DOWNLOAD_RATELIMIT
if not self.can_prepare_data:
- context['time_until_can_prepare'] = (
- settings.DMOJ_USER_DATA_DOWNLOAD_RATELIMIT - (self._now - self.request.profile.data_last_downloaded)
+ context[
+ 'time_until_can_prepare'
+ ] = settings.DMOJ_USER_DATA_DOWNLOAD_RATELIMIT - (
+ self._now - self.request.profile.data_last_downloaded
)
return context
@@ -337,20 +454,30 @@ def get(self, request, *args, **kwargs):
response = HttpResponse()
if hasattr(settings, 'DMOJ_USER_DATA_INTERNAL'):
- url_path = '%s/%s.zip' % (settings.DMOJ_USER_DATA_INTERNAL, self.request.profile.id)
+ url_path = '%s/%s.zip' % (
+ settings.DMOJ_USER_DATA_INTERNAL,
+ self.request.profile.id,
+ )
else:
url_path = None
add_file_response(request, response, url_path, self.data_path)
response['Content-Type'] = 'application/zip'
- response['Content-Disposition'] = 'attachment; filename=%s-data.zip' % self.request.user.username
+ response['Content-Disposition'] = (
+ 'attachment; filename=%s-data.zip' % self.request.user.username
+ )
return response
@login_required
def edit_profile(request):
if request.profile.mute:
- return generic_message(request, _("Can't edit profile"), _('Your part is silent, little toad.'), status=403)
+ return generic_message(
+ request,
+ _("Can't edit profile"),
+ _('Your part is silent, little toad.'),
+ status=403,
+ )
if request.method == 'POST':
form = ProfileForm(request.POST, instance=request.profile, user=request.user)
if form.is_valid():
@@ -361,15 +488,28 @@ def edit_profile(request):
if newsletter_id is not None:
try:
- subscription = Subscription.objects.get(user=request.user, newsletter_id=newsletter_id)
+ subscription = Subscription.objects.get(
+ user=request.user, newsletter_id=newsletter_id
+ )
except Subscription.DoesNotExist:
if form.cleaned_data['newsletter']:
- Subscription(user=request.user, newsletter_id=newsletter_id, subscribed=True).save()
+ Subscription(
+ user=request.user,
+ newsletter_id=newsletter_id,
+ subscribed=True,
+ ).save()
else:
if subscription.subscribed != form.cleaned_data['newsletter']:
- subscription.update(('unsubscribe', 'subscribe')[form.cleaned_data['newsletter']])
-
- perm = Permission.objects.get(codename='test_site', content_type=ContentType.objects.get_for_model(Profile))
+ subscription.update(
+ ('unsubscribe', 'subscribe')[
+ form.cleaned_data['newsletter']
+ ]
+ )
+
+ perm = Permission.objects.get(
+ codename='test_site',
+ content_type=ContentType.objects.get_for_model(Profile),
+ )
if form.cleaned_data['test_site']:
request.user.user_permissions.add(perm)
else:
@@ -380,21 +520,29 @@ def edit_profile(request):
form = ProfileForm(instance=request.profile, user=request.user)
if newsletter_id is not None:
try:
- subscription = Subscription.objects.get(user=request.user, newsletter_id=newsletter_id)
+ subscription = Subscription.objects.get(
+ user=request.user, newsletter_id=newsletter_id
+ )
except Subscription.DoesNotExist:
form.fields['newsletter'].initial = False
else:
form.fields['newsletter'].initial = subscription.subscribed
form.fields['test_site'].initial = request.user.has_perm('judge.test_site')
- return render(request, 'user/edit-profile.html', {
- 'require_staff_2fa': settings.DMOJ_REQUIRE_STAFF_2FA,
- 'form': form, 'title': _('Edit profile'), 'profile': request.profile,
- 'can_download_data': bool(settings.DMOJ_USER_DATA_DOWNLOAD),
- 'has_math_config': bool(settings.MATHOID_URL),
- 'ignore_user_script': True,
- 'TIMEZONE_MAP': settings.TIMEZONE_MAP,
- })
+ return render(
+ request,
+ 'user/edit-profile.html',
+ {
+ 'require_staff_2fa': settings.DMOJ_REQUIRE_STAFF_2FA,
+ 'form': form,
+ 'title': _('Edit profile'),
+ 'profile': request.profile,
+ 'can_download_data': bool(settings.DMOJ_USER_DATA_DOWNLOAD),
+ 'has_math_config': bool(settings.MATHOID_URL),
+ 'ignore_user_script': True,
+ 'TIMEZONE_MAP': settings.TIMEZONE_MAP,
+ },
+ )
@require_POST
@@ -429,7 +577,13 @@ def generate_scratch_codes(request):
return JsonResponse({'data': {'codes': profile.generate_scratch_codes()}})
-class UserList(QueryStringSortMixin, InfinitePaginationMixin, DiggPaginatorMixin, TitleMixin, ListView):
+class UserList(
+ QueryStringSortMixin,
+ InfinitePaginationMixin,
+ DiggPaginatorMixin,
+ TitleMixin,
+ ListView,
+):
model = Profile
title = gettext_lazy('Leaderboard')
context_object_name = 'users'
@@ -440,9 +594,20 @@ class UserList(QueryStringSortMixin, InfinitePaginationMixin, DiggPaginatorMixin
default_sort = '-performance_points'
def get_queryset(self):
- return (Profile.objects.filter(is_unlisted=False).order_by(self.order, 'id').select_related('user')
- .only('display_rank', 'user__username', 'username_display_override', 'points', 'rating',
- 'performance_points', 'problem_count'))
+ return (
+ Profile.objects.filter(is_unlisted=False)
+ .order_by(self.order, 'id')
+ .select_related('user')
+ .only(
+ 'display_rank',
+ 'user__username',
+ 'username_display_override',
+ 'points',
+ 'rating',
+ 'performance_points',
+ 'problem_count',
+ )
+ )
def get_context_data(self, **kwargs):
context = super(UserList, self).get_context_data(**kwargs)
@@ -472,7 +637,9 @@ def users(request):
participation = request.profile.current_contest
if participation is not None:
contest = participation.contest
- return FixedContestRanking.as_view(contest=contest)(request, contest=contest.key)
+ return FixedContestRanking.as_view(contest=contest)(
+ request, contest=contest.key
+ )
return user_list_view(request)
@@ -482,12 +649,19 @@ def user_ranking_redirect(request):
except KeyError:
raise Http404()
user = get_object_or_404(Profile, user__username=username)
- rank = Profile.objects.filter(is_unlisted=False, performance_points__gt=user.performance_points).count()
+ rank = Profile.objects.filter(
+ is_unlisted=False, performance_points__gt=user.performance_points
+ ).count()
rank += Profile.objects.filter(
- is_unlisted=False, performance_points__exact=user.performance_points, id__lt=user.id,
+ is_unlisted=False,
+ performance_points__exact=user.performance_points,
+ id__lt=user.id,
).count()
page = rank // UserList.paginate_by
- return HttpResponseRedirect('%s%s#!%s' % (reverse('user_list'), '?page=%d' % (page + 1) if page else '', username))
+ return HttpResponseRedirect(
+ '%s%s#!%s'
+ % (reverse('user_list'), '?page=%d' % (page + 1) if page else '', username)
+ )
class UserLogoutView(TitleMixin, TemplateView):
@@ -510,10 +684,19 @@ class CustomPasswordResetView(PasswordResetView):
def post(self, request, *args, **kwargs):
key = f'pwreset!{request.META["REMOTE_ADDR"]}'
- cache.add(key, 0, timeout=settings.DMOJ_PASSWORD_RESET_LIMIT_WINDOW * MINUTES_TO_SECONDS)
+ cache.add(
+ key,
+ 0,
+ timeout=settings.DMOJ_PASSWORD_RESET_LIMIT_WINDOW * MINUTES_TO_SECONDS,
+ )
if cache.incr(key) > settings.DMOJ_PASSWORD_RESET_LIMIT_COUNT:
- return HttpResponse(_('You have sent too many password reset requests. Please try again later.'),
- content_type='text/plain', status=429)
+ return HttpResponse(
+ _(
+ 'You have sent too many password reset requests. Please try again later.'
+ ),
+ content_type='text/plain',
+ status=429,
+ )
return super().post(request, *args, **kwargs)
@@ -532,10 +715,14 @@ class EmailChangeRequestView(LoginRequiredMixin, TitleMixin, FormView):
def form_valid(self, form):
signer = signing.TimestampSigner()
new_email = form.cleaned_data['email']
- activation_key = base64.urlsafe_b64encode(signer.sign_object({
- 'id': self.request.user.id,
- 'email': new_email,
- }).encode()).decode()
+ activation_key = base64.urlsafe_b64encode(
+ signer.sign_object(
+ {
+ 'id': self.request.user.id,
+ 'email': new_email,
+ }
+ ).encode()
+ ).decode()
current_site = get_current_site(self.request)
context = {
@@ -576,10 +763,17 @@ def get_form_kwargs(self):
def post(self, request, *args, **kwargs):
key = f'emailchange!{request.META["REMOTE_ADDR"]}'
- cache.add(key, 0, timeout=settings.DMOJ_EMAIL_CHANGE_LIMIT_WINDOW * MINUTES_TO_SECONDS)
+ cache.add(
+ key, 0, timeout=settings.DMOJ_EMAIL_CHANGE_LIMIT_WINDOW * MINUTES_TO_SECONDS
+ )
if cache.incr(key) > settings.DMOJ_EMAIL_CHANGE_LIMIT_COUNT:
- return HttpResponse(_('You have sent too many email change requests. Please try again later.'),
- content_type='text/plain', status=429)
+ return HttpResponse(
+ _(
+ 'You have sent too many email change requests. Please try again later.'
+ ),
+ content_type='text/plain',
+ status=429,
+ )
return super().post(request, *args, **kwargs)
@@ -595,35 +789,48 @@ def update_user_email(self, request, activation_key):
max_age=settings.DMOJ_EMAIL_CHANGE_EXPIRY_MINUTES * MINUTES_TO_SECONDS,
)
except (binascii.Error, signing.BadSignature):
- raise self.EmailChangeFailedError(_('Invalid activation key. Please try again.'))
+ raise self.EmailChangeFailedError(
+ _('Invalid activation key. Please try again.')
+ )
except signing.SignatureExpired:
- raise self.EmailChangeFailedError(_('This request has expired. Please try again.'))
+ raise self.EmailChangeFailedError(
+ _('This request has expired. Please try again.')
+ )
if data['id'] != request.user.id:
raise self.EmailChangeFailedError(
- _('Please try again while logged in to the account this email change was originally requested from.'),
+ _(
+ 'Please try again while logged in to the account this email change was originally requested from.'
+ ),
)
from_email = request.user.email
to_email = data['email']
with revisions.create_revision(atomic=True):
if User.objects.filter(email=to_email).exists():
raise self.EmailChangeFailedError(
- _('The email you originally requested has since been registered by another user. '
- 'Please try again with a new email.'),
+ _(
+ 'The email you originally requested has since been registered by another user. '
+ 'Please try again with a new email.'
+ ),
)
request.user.email = to_email
request.user.save()
revisions.set_user(request.user)
- revisions.set_comment(_('Changed email address from %s to %s') % (from_email, to_email))
+ revisions.set_comment(
+ _('Changed email address from %s to %s') % (from_email, to_email)
+ )
return to_email
def get(self, request, *args, **kwargs):
try:
to_email = self.update_user_email(request, kwargs['activation_key'])
except self.EmailChangeFailedError as e:
- return generic_message(request, _('Email change failed'), str(e), status=403)
+ return generic_message(
+ request, _('Email change failed'), str(e), status=403
+ )
else:
return generic_message(
request,
_('Email successfully changed'),
- _('The email attached to your account has been changed to %s.') % to_email,
+ _('The email attached to your account has been changed to %s.')
+ % to_email,
)
diff --git a/judge/views/widgets.py b/judge/views/widgets.py
index f1aec4ef0f..2dc30c1fa0 100644
--- a/judge/views/widgets.py
+++ b/judge/views/widgets.py
@@ -6,7 +6,12 @@
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.files.storage import default_storage
-from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseRedirect
+from django.http import (
+ HttpResponse,
+ HttpResponseBadRequest,
+ HttpResponseForbidden,
+ HttpResponseRedirect,
+)
from django.views.decorators.http import require_POST
from martor.api import imgur_uploader
@@ -33,7 +38,11 @@ def rejudge_submission(request):
redirect = request.POST.get('path', None)
- return HttpResponseRedirect(redirect) if redirect else HttpResponse('success', content_type='text/plain')
+ return (
+ HttpResponseRedirect(redirect)
+ if redirect
+ else HttpResponse('success', content_type='text/plain')
+ )
def django_uploader(image):
@@ -42,8 +51,11 @@ def django_uploader(image):
ext = '.png'
name = str(uuid.uuid4()) + ext
default_storage.save(os.path.join(settings.MARTOR_UPLOAD_MEDIA_DIR, name), image)
- url_base = getattr(settings, 'MARTOR_UPLOAD_URL_PREFIX',
- urljoin(settings.MEDIA_URL, settings.MARTOR_UPLOAD_MEDIA_DIR))
+ url_base = getattr(
+ settings,
+ 'MARTOR_UPLOAD_URL_PREFIX',
+ urljoin(settings.MEDIA_URL, settings.MARTOR_UPLOAD_MEDIA_DIR),
+ )
if not url_base.endswith('/'):
url_base += '/'
return json.dumps({'status': 200, 'name': '', 'link': urljoin(url_base, name)})
@@ -51,7 +63,11 @@ def django_uploader(image):
@login_required
def martor_image_uploader(request):
- if request.method != 'POST' or not request.is_ajax() or 'markdown-image-upload' not in request.FILES:
+ if (
+ request.method != 'POST'
+ or not request.is_ajax()
+ or 'markdown-image-upload' not in request.FILES
+ ):
return HttpResponseBadRequest('Invalid request')
image = request.FILES['markdown-image-upload']
diff --git a/judge/widgets/checkbox.py b/judge/widgets/checkbox.py
index 0a7eebfb48..90fbd2ff29 100644
--- a/judge/widgets/checkbox.py
+++ b/judge/widgets/checkbox.py
@@ -11,12 +11,20 @@ def render(self, name, value, attrs=None, renderer=None):
select_all_id = attrs['id'] + '_all'
select_all_name = name + '_all'
- original = super(CheckboxSelectMultipleWithSelectAll, self).render(name, value, attrs, renderer)
+ original = super(CheckboxSelectMultipleWithSelectAll, self).render(
+ name, value, attrs, renderer
+ )
template = get_template('widgets/select_all.html')
- return mark_safe(template.render({
- 'original_widget': original,
- 'select_all_id': select_all_id,
- 'select_all_name': select_all_name,
- 'all_selected': all(choice[0] in value for choice in self.choices) if value else False,
- 'empty': not self.choices,
- }))
+ return mark_safe(
+ template.render(
+ {
+ 'original_widget': original,
+ 'select_all_id': select_all_id,
+ 'select_all_name': select_all_name,
+ 'all_selected': all(choice[0] in value for choice in self.choices)
+ if value
+ else False,
+ 'empty': not self.choices,
+ }
+ )
+ )
diff --git a/judge/widgets/martor.py b/judge/widgets/martor.py
index 9f8738e0bf..fa035abaa7 100644
--- a/judge/widgets/martor.py
+++ b/judge/widgets/martor.py
@@ -1,4 +1,7 @@
-from martor.widgets import AdminMartorWidget as OldAdminMartorWidget, MartorWidget as OldMartorWidget
+from martor.widgets import (
+ AdminMartorWidget as OldAdminMartorWidget,
+ MartorWidget as OldMartorWidget,
+)
__all__ = ['MartorWidget', 'AdminMartorWidget']
diff --git a/judge/widgets/mixins.py b/judge/widgets/mixins.py
index c6e97e41ab..39204d4f3b 100644
--- a/judge/widgets/mixins.py
+++ b/judge/widgets/mixins.py
@@ -7,17 +7,21 @@
class CompressorWidgetMixin(object):
- __template_css = dedent("""\
+ __template_css = dedent(
+ """\
{% compress css %}
{{ media.css }}
{% endcompress %}
- """)
+ """
+ )
- __template_js = dedent("""\
+ __template_js = dedent(
+ """\
{% compress js %}
{{ media.js }}
{% endcompress %}
- """)
+ """
+ )
__templates = {
(False, False): Template(''),
@@ -35,6 +39,7 @@ class CompressorWidgetMixin(object):
pass
else:
if getattr(settings, 'COMPRESS_ENABLED', not settings.DEBUG):
+
@property
def media(self):
media = super().media
@@ -42,6 +47,10 @@ def media(self):
result = html.fromstring(template.render(Context({'media': media})))
return forms.Media(
- css={'all': [result.find('.//link').get('href')]} if self.compress_css else media._css,
- js=[result.find('.//script').get('src')] if self.compress_js else media._js,
+ css={'all': [result.find('.//link').get('href')]}
+ if self.compress_css
+ else media._css,
+ js=[result.find('.//script').get('src')]
+ if self.compress_js
+ else media._js,
)
diff --git a/judge/widgets/pagedown.py b/judge/widgets/pagedown.py
index dc07359215..35688122c8 100644
--- a/judge/widgets/pagedown.py
+++ b/judge/widgets/pagedown.py
@@ -14,6 +14,7 @@
MathJaxPagedownWidget = None
HeavyPreviewPageDownWidget = None
else:
+
class PagedownWidget(CompressorWidgetMixin, OldPagedownWidget):
# The goal here is to compress all the pagedown JS into one file.
# We do not want any further compress down the chain, because
@@ -25,7 +26,6 @@ def __init__(self, *args, **kwargs):
kwargs.setdefault('css', ())
super(PagedownWidget, self).__init__(*args, **kwargs)
-
class MathJaxPagedownWidget(PagedownWidget):
class Media:
js = [
@@ -34,7 +34,6 @@ class Media:
'pagedown_math.js',
]
-
class HeavyPreviewPageDownWidget(PagedownWidget):
def __init__(self, *args, **kwargs):
kwargs.setdefault('template', 'pagedown.html')
@@ -50,7 +49,9 @@ def render(self, name, value, attrs=None, renderer=None):
if 'class' not in final_attrs:
final_attrs['class'] = ''
final_attrs['class'] += ' wmd-input'
- return get_template(self.template).render(self.get_template_context(final_attrs, value))
+ return get_template(self.template).render(
+ self.get_template_context(final_attrs, value)
+ )
def get_template_context(self, attrs, value):
return {
diff --git a/judge/widgets/select2.py b/judge/widgets/select2.py
index 8ae0659851..c1b47d4709 100644
--- a/judge/widgets/select2.py
+++ b/judge/widgets/select2.py
@@ -46,10 +46,18 @@
from django.forms.models import ModelChoiceIterator
from django.urls import reverse_lazy
-__all__ = ['Select2Widget', 'Select2MultipleWidget', 'Select2TagWidget',
- 'HeavySelect2Widget', 'HeavySelect2MultipleWidget', 'HeavySelect2TagWidget',
- 'AdminSelect2Widget', 'AdminSelect2MultipleWidget', 'AdminHeavySelect2Widget',
- 'AdminHeavySelect2MultipleWidget']
+__all__ = [
+ 'Select2Widget',
+ 'Select2MultipleWidget',
+ 'Select2TagWidget',
+ 'HeavySelect2Widget',
+ 'HeavySelect2MultipleWidget',
+ 'HeavySelect2TagWidget',
+ 'AdminSelect2Widget',
+ 'AdminSelect2MultipleWidget',
+ 'AdminHeavySelect2Widget',
+ 'AdminHeavySelect2MultipleWidget',
+]
class Select2Mixin(object):
@@ -102,7 +110,11 @@ class AdminSelect2Mixin(Select2Mixin):
@property
def media(self):
return forms.Media(
- js=['admin/js/jquery.init.js', settings.SELECT2_JS_URL, 'django_select2.js'],
+ js=[
+ 'admin/js/jquery.init.js',
+ settings.SELECT2_JS_URL,
+ 'django_select2.js',
+ ],
css={'screen': [settings.SELECT2_CSS_URL, 'select2-dmoj.css']},
)
@@ -212,11 +224,14 @@ def format_value(self, value):
result = super(HeavySelect2Mixin, self).format_value(value)
if isinstance(self.choices, ModelChoiceIterator):
chosen = copy(self.choices)
- chosen.queryset = chosen.queryset.filter(pk__in=[
- int(i) for i in result if isinstance(i, int) or i.isdigit()
- ])
+ chosen.queryset = chosen.queryset.filter(
+ pk__in=[int(i) for i in result if isinstance(i, int) or i.isdigit()]
+ )
# https://code.djangoproject.com/ticket/33155
- self.choices = {(value if isinstance(value, str) else value.value, label) for value, label in chosen}
+ self.choices = {
+ (value if isinstance(value, str) else value.value, label)
+ for value, label in chosen
+ }
return result
diff --git a/manage.py b/manage.py
index adaffbd994..9a3432b519 100755
--- a/manage.py
+++ b/manage.py
@@ -11,4 +11,5 @@
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dmoj.settings')
from django.core.management import execute_from_command_line
+
execute_from_command_line(sys.argv)