From d2f4549153b4f5fa4e9c7774773dcd7b62f1e8b3 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 6 Jan 2024 04:00:19 -0500 Subject: [PATCH] so smart This reverts commit c6701ee0 --- .ci.settings.py | 6 +- django_ace/widgets.py | 23 +- dmoj/celery.py | 10 +- dmoj/settings.py | 124 +- dmoj/urls.py | 1198 ++----- dmoj/wsgi.py | 6 +- dmoj/wsgi_async.py | 5 +- dmoj_bridge_async.py | 5 +- judge/admin/__init__.py | 46 +- judge/admin/comments.py | 28 +- judge/admin/contest.py | 347 +- judge/admin/interface.py | 70 +- judge/admin/organization.py | 56 +- judge/admin/problem.py | 210 +- judge/admin/profile.py | 81 +- judge/admin/runtime.py | 76 +- judge/admin/submission.py | 229 +- judge/admin/taxon.py | 14 +- judge/admin/ticket.py | 32 +- judge/bridge/base_handler.py | 37 +- judge/bridge/daemon.py | 13 +- judge/bridge/django_handler.py | 9 +- judge/bridge/echo_test_client.py | 7 +- judge/bridge/echo_test_server.py | 5 +- judge/bridge/judge_handler.py | 555 +-- judge/bridge/judge_list.py | 59 +- judge/bridge/server.py | 4 +- judge/comments.py | 56 +- judge/contest_format/atcoder.py | 58 +- judge/contest_format/default.py | 50 +- judge/contest_format/ecoo.py | 91 +- judge/contest_format/icpc.py | 64 +- judge/contest_format/ioi.py | 17 +- judge/contest_format/legacy_ioi.py | 65 +- judge/dblock.py | 10 +- judge/event_poster.py | 3 - judge/event_poster_amqp.py | 11 +- judge/event_poster_ws.py | 8 +- judge/feed.py | 8 +- judge/forms.py | 150 +- judge/fulltext.py | 24 +- judge/highlight_code.py | 9 +- judge/jinja2/__init__.py | 18 +- judge/jinja2/datetime.py | 8 +- judge/jinja2/filesize.py | 6 +- judge/jinja2/gravatar.py | 6 +- judge/jinja2/markdown/__init__.py | 68 +- judge/jinja2/markdown/bleach_whitelist.py | 1669 ++------- judge/jinja2/markdown/math.py | 4 +- judge/jinja2/markdown/test_markdown.py | 34 +- judge/jinja2/reference.py | 29 +- judge/jinja2/registry.py | 2 - judge/jinja2/render.py | 8 +- judge/jinja2/social.py | 25 +- judge/jinja2/spaceless.py | 4 +- judge/jinja2/submission.py | 28 +- judge/judgeapi.py | 108 +- judge/lxml_tree.py | 4 +- judge/management/commands/adduser.py | 24 +- judge/management/commands/copy_language.py | 12 +- .../management/commands/generate_api_token.py | 4 +- judge/management/commands/generate_sitemap.py | 33 +- judge/management/commands/makedmojmessages.py | 90 +- .../management/commands/move_user_content.py | 4 +- judge/management/commands/render_pdf.py | 39 +- judge/management/commands/runmoss.py | 16 +- judge/middleware.py | 53 +- .../0001_squashed_0084_contest_formats.py | 3038 ++--------------- judge/migrations/0085_submission_source.py | 45 +- judge/migrations/0086_rating_ceiling.py | 15 +- .../0087_problem_resource_limits.py | 15 +- judge/migrations/0088_private_contests.py | 30 +- .../migrations/0089_submission_to_contest.py | 17 +- .../migrations/0090_fix_contest_visibility.py | 10 +- .../0091_compiler_message_ansi2html.py | 1 + judge/migrations/0092_contest_clone.py | 15 +- judge/migrations/0093_contest_moss.py | 40 +- .../0095_organization_logo_override.py | 9 +- .../0096_profile_language_set_default.py | 8 +- .../0097_participation_is_disqualified.py | 13 +- .../0098_view_contest_scoreboard.py | 9 +- .../migrations/0099_contest_problem_label.py | 23 +- .../0100_contest_visiblity_permission.py | 18 +- .../migrations/0101_submission_judged_date.py | 5 +- judge/migrations/0102_api_token.py | 13 +- ...03_contest_participation_tiebreak_field.py | 1 + .../migrations/0104_contestproblem_maxsubs.py | 13 +- judge/migrations/0105_webauthn.py | 34 +- judge/migrations/0106_user_data_download.py | 5 +- judge/migrations/0107_submission_lock.py | 47 +- judge/migrations/0108_bleach_problems.py | 21 +- judge/migrations/0109_scratch_codes.py | 15 +- .../0110_default_output_prefix_override.py | 8 +- .../migrations/0111_blank_assignees_ticket.py | 8 +- judge/migrations/0112_language_extensions.py | 10 +- .../migrations/0113_contest_decimal_points.py | 11 +- .../migrations/0114_remove_org_registrant.py | 1 + .../0115_contest_scoreboard_visibility.py | 21 +- .../0116_contest_curator_and_tester.py | 21 +- .../0117_remove_private_messages.py | 1 + judge/migrations/0118_convert_to_dates.py | 16 +- judge/migrations/0119_hide_problem_authors.py | 7 +- judge/migrations/0120_totp_no_reuse.py | 1 + .../0121_per_problem_sub_access_control.py | 13 +- .../0122_username_display_override.py | 8 +- .../migrations/0123_contest_rating_elo_mmr.py | 99 +- .../0124_contest_show_short_display.py | 7 +- judge/migrations/0125_organization_classes.py | 108 +- judge/migrations/0126_infer_private_bools.py | 1 + .../migrations/0127_tester_see_scoreboard.py | 7 +- .../0128_limit_join_organizations.py | 13 +- judge/migrations/0129_see_scoreboard_subs.py | 15 +- .../0130_blogpost_change_visibility.py | 10 +- judge/migrations/0131_spectate_contests.py | 28 +- judge/migrations/0132_no_self_vote.py | 5 +- .../migrations/0133_add_problem_data_hints.py | 1311 +------ .../0134_add_voting_functionality.py | 80 +- judge/migrations/0135_disable_judge.py | 7 +- .../0136_remove_zombie_editorials.py | 9 +- judge/migrations/0137_profile_site_theme.py | 12 +- judge/migrations/0138_dark_ace_theme.py | 42 +- judge/migrations/0139_user_index_refactor.py | 15 +- .../0140_submission_index_refactor.py | 40 +- .../migrations/0141_submission_extra_index.py | 23 +- .../migrations/0142_comment_revision_count.py | 11 +- .../0143_contest_problem_rank_index.py | 6 +- .../0144_submission_index_cleanup.py | 45 +- judge/models/__init__.py | 62 +- judge/models/comment.py | 91 +- judge/models/contest.py | 702 +--- judge/models/interface.py | 23 +- judge/models/problem.py | 536 +-- judge/models/problem_data.py | 116 +- judge/models/profile.py | 459 +-- judge/models/runtime.py | 194 +- judge/models/submission.py | 168 +- judge/models/tests/test_blogpost.py | 26 +- judge/models/tests/test_contest.py | 152 +- judge/models/tests/test_problem.py | 144 +- judge/models/tests/test_profile.py | 28 +- judge/models/tests/test_submission.py | 34 +- judge/models/tests/util.py | 31 +- judge/models/ticket.py | 45 +- judge/performance_points.py | 69 +- judge/ratings.py | 130 +- judge/signals.py | 116 +- judge/sitemap.py | 24 +- judge/social_auth.py | 51 +- judge/tasks/contest.py | 37 +- judge/tasks/submission.py | 27 +- judge/tasks/user.py | 35 +- judge/template_context.py | 25 +- judge/templatetags/strings.py | 2 +- judge/user_log.py | 7 +- judge/user_translations.py | 6 +- judge/utils/camo.py | 17 +- judge/utils/caniuse.py | 15 +- judge/utils/diggpaginator.py | 50 +- judge/utils/infinite_paginator.py | 33 +- judge/utils/mail.py | 16 +- judge/utils/mathoid.py | 55 +- judge/utils/pdfoid.py | 7 +- judge/utils/problem_data.py | 31 +- judge/utils/problems.py | 137 +- judge/utils/raw_sql.py | 44 +- judge/utils/recaptcha.py | 1 - judge/utils/safe_translations.py | 10 +- judge/utils/stats.py | 38 +- judge/utils/subscription.py | 4 +- judge/utils/tests/test_infinite_paginator.py | 69 +- judge/utils/texoid.py | 28 +- judge/utils/tickets.py | 10 +- judge/utils/timedelta.py | 53 +- judge/utils/views.py | 65 +- judge/views/api/__init__.py | 12 +- judge/views/api/api_v2.py | 244 +- judge/views/blog.py | 100 +- judge/views/comment.py | 58 +- judge/views/contests.py | 607 +--- judge/views/error.py | 43 +- judge/views/mailgun.py | 48 +- judge/views/organization.py | 325 +- judge/views/preview.py | 8 +- judge/views/problem.py | 515 +-- judge/views/problem_data.py | 161 +- judge/views/problem_manage.py | 83 +- judge/views/ranked_submission.py | 80 +- judge/views/register.py | 85 +- judge/views/select2.py | 96 +- judge/views/stats.py | 72 +- judge/views/status.py | 50 +- judge/views/submission.py | 507 +-- judge/views/tasks.py | 44 +- judge/views/ticket.py | 221 +- judge/views/two_factor.py | 65 +- judge/views/user.py | 425 +-- judge/views/widgets.py | 26 +- judge/widgets/checkbox.py | 24 +- judge/widgets/martor.py | 5 +- judge/widgets/mixins.py | 21 +- judge/widgets/pagedown.py | 7 +- judge/widgets/select2.py | 33 +- manage.py | 1 - 203 files changed, 4085 insertions(+), 15559 deletions(-) diff --git a/.ci.settings.py b/.ci.settings.py index 49b693ef46..637c918399 100644 --- a/.ci.settings.py +++ b/.ci.settings.py @@ -2,7 +2,11 @@ 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 c0b90d7abd..2f521bf7f7 100644 --- a/django_ace/widgets.py +++ b/django_ace/widgets.py @@ -11,17 +11,8 @@ 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 @@ -56,17 +47,13 @@ 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 = ( - '
' - '
%s
' - ) % html + html = ('
' + '
%s
') % html return mark_safe(html) diff --git a/dmoj/celery.py b/dmoj/celery.py index 7f7ac1a0fd..e1da640642 100644 --- a/dmoj/celery.py +++ b/dmoj/celery.py @@ -7,7 +7,6 @@ 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'): @@ -24,10 +23,5 @@ @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 231104c45e..cea555a59e 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,26 +64,11 @@ 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) @@ -173,9 +158,7 @@ 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 @@ -375,8 +358,7 @@ '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', @@ -428,76 +410,15 @@ ] 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 = { @@ -511,18 +432,7 @@ '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'], } @@ -621,9 +531,7 @@ 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 2976a6a716..08b7812ed7 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -10,164 +10,69 @@ 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'), ] @@ -180,782 +85,305 @@ 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), - ] - ), - ), -] -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', + 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'] + 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 c09eda8295..6bec753460 100644 --- a/dmoj/wsgi.py +++ b/dmoj/wsgi.py @@ -1,5 +1,4 @@ import os - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dmoj.settings') try: @@ -9,8 +8,5 @@ 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 cc74147934..ec114d1fd8 100644 --- a/dmoj/wsgi_async.py +++ b/dmoj/wsgi_async.py @@ -8,8 +8,5 @@ # 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 f224d0d67b..376f8cf8d0 100644 --- a/dmoj_bridge_async.py +++ b/dmoj_bridge_async.py @@ -9,12 +9,9 @@ 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 a27ce6517c..aa3d475ccc 100644 --- a/judge/admin/__init__.py +++ b/judge/admin/__init__.py @@ -4,52 +4,18 @@ 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 9b9a259f07..a0e28b5633 100644 --- a/judge/admin/comments.py +++ b/judge/admin/comments.py @@ -15,9 +15,7 @@ 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')}), } @@ -42,28 +40,16 @@ 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 d172b618d6..59c25d103a 100644 --- a/judge/admin/contest.py +++ b/judge/admin/contest.py @@ -13,24 +13,11 @@ 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): @@ -44,8 +31,7 @@ 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): @@ -78,16 +64,8 @@ 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 @@ -95,11 +73,8 @@ 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): @@ -107,9 +82,8 @@ 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 @@ -117,9 +91,7 @@ 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 = { @@ -127,121 +99,39 @@ 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 @@ -254,9 +144,8 @@ 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) @@ -271,9 +160,7 @@ 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 = [] @@ -297,18 +184,12 @@ 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'): @@ -317,9 +198,7 @@ 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 @@ -341,111 +220,67 @@ 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'): @@ -454,9 +289,7 @@ 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')) @@ -468,9 +301,7 @@ 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) @@ -478,15 +309,14 @@ 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 @@ -502,15 +332,7 @@ 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') @@ -518,20 +340,9 @@ 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): @@ -545,15 +356,9 @@ 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 8c09797231..f49c3a654c 100644 --- a/judge/admin/interface.py +++ b/judge/admin/interface.py @@ -11,11 +11,7 @@ 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): @@ -40,9 +36,7 @@ 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() @@ -51,11 +45,7 @@ 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): @@ -71,24 +61,15 @@ 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',)}), ) @@ -123,25 +104,15 @@ 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): @@ -164,14 +135,7 @@ 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') @@ -194,14 +158,8 @@ 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 493b9a031c..ff5fab5f0b 100644 --- a/judge/admin/organization.py +++ b/judge/admin/organization.py @@ -18,16 +18,7 @@ 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 @@ -36,26 +27,22 @@ 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 = [] @@ -68,9 +55,7 @@ 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 @@ -78,26 +63,14 @@ 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 @@ -106,11 +79,8 @@ 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 b5f0c40d29..c78c0981f6 100644 --- a/judge/admin/problem.py +++ b/judge/admin/problem.py @@ -10,29 +10,15 @@ 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) @@ -40,34 +26,22 @@ 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')}), } @@ -75,9 +49,7 @@ 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): @@ -99,11 +71,7 @@ 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): @@ -120,12 +88,8 @@ 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')}), } @@ -138,11 +102,7 @@ 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): @@ -154,41 +114,21 @@ 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')}), @@ -196,27 +136,10 @@ 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 @@ -257,64 +180,39 @@ 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: @@ -324,9 +222,7 @@ 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) @@ -338,18 +234,16 @@ 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): @@ -358,9 +252,7 @@ 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 fa2c829359..5e267bf93e 100644 --- a/judge/admin/profile.py +++ b/judge/admin/profile.py @@ -17,16 +17,10 @@ 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 = { @@ -34,9 +28,7 @@ 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')}), } @@ -45,11 +37,7 @@ 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: @@ -67,37 +55,12 @@ 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) @@ -138,11 +101,8 @@ 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): @@ -166,23 +126,16 @@ 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 f1704bc784..3c756527c8 100644 --- a/judge/admin/runtime.py +++ b/judge/admin/runtime.py @@ -19,18 +19,8 @@ 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 @@ -38,8 +28,7 @@ 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 @@ -47,10 +36,8 @@ 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): @@ -76,52 +59,25 @@ 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) @@ -137,9 +93,7 @@ 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 be009a68e9..aa3889d304 100644 --- a/judge/admin/submission.py +++ b/judge/admin/submission.py @@ -14,25 +14,14 @@ 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): @@ -79,9 +68,7 @@ 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): @@ -89,34 +76,25 @@ 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 @@ -130,54 +108,23 @@ 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 @@ -186,25 +133,14 @@ 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): @@ -218,111 +154,58 @@ 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): @@ -357,15 +240,10 @@ 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 [ @@ -373,14 +251,11 @@ 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 d0a69370eb..aa245d7dc7 100644 --- a/judge/admin/taxon.py +++ b/judge/admin/taxon.py @@ -12,8 +12,7 @@ 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): @@ -26,9 +25,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(ProblemGroupAdmin, self).get_form(request, obj, **kwargs) @@ -38,8 +35,7 @@ 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): @@ -52,7 +48,5 @@ 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 4507fe5498..737bee53cd 100644 --- a/judge/admin/ticket.py +++ b/judge/admin/ticket.py @@ -4,22 +4,14 @@ 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')}), } @@ -32,25 +24,13 @@ 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 85ae2685b1..e3ed9c574a 100644 --- a/judge/bridge/base_handler.py +++ b/judge/bridge/base_handler.py @@ -70,12 +70,8 @@ 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 = [] @@ -153,9 +149,7 @@ 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: @@ -163,8 +157,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 @@ -180,28 +174,17 @@ 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 048aa4aecc..ce8a702dbd 100644 --- a/judge/bridge/daemon.py +++ b/judge/bridge/daemon.py @@ -20,17 +20,12 @@ 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 515eeee7b5..cdde06e0dd 100644 --- a/judge/bridge/django_handler.py +++ b/judge/bridge/django_handler.py @@ -28,9 +28,7 @@ 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'} @@ -50,10 +48,7 @@ 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 762edfddcc..8fec692aaf 100644 --- a/judge/bridge/echo_test_client.py +++ b/judge/bridge/echo_test_client.py @@ -19,14 +19,13 @@ 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) @@ -59,8 +58,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 412688c8fa..59e21fa223 100644 --- a/judge/bridge/echo_test_server.py +++ b/judge/bridge/echo_test_server.py @@ -11,10 +11,7 @@ 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 b13b94e04f..073de27262 100644 --- a/judge/bridge/judge_handler.py +++ b/judge/bridge/judge_handler.py @@ -13,25 +13,14 @@ 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(): @@ -92,32 +81,16 @@ 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: @@ -127,19 +100,11 @@ 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 @@ -159,29 +124,15 @@ 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) @@ -189,16 +140,10 @@ 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): @@ -227,11 +172,8 @@ 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): @@ -241,60 +183,25 @@ 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 @@ -319,29 +226,25 @@ 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): @@ -358,36 +261,18 @@ 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: @@ -422,9 +307,7 @@ 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): @@ -437,40 +320,23 @@ 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']) @@ -481,11 +347,7 @@ 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 @@ -533,22 +395,12 @@ 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 @@ -560,158 +412,77 @@ 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']) @@ -721,42 +492,23 @@ 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 = [] @@ -789,29 +541,15 @@ def on_test_case( 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 @@ -828,22 +566,17 @@ def on_test_case( 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() @@ -884,34 +617,20 @@ 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 5d8cd9a2f7..b65d0ddb82 100644 --- a/judge/bridge/judge_list.py +++ b/judge/bridge/judge_list.py @@ -20,9 +20,7 @@ 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 = {} @@ -35,15 +33,8 @@ 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 @@ -52,18 +43,10 @@ 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 @@ -148,30 +131,14 @@ 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: @@ -182,13 +149,7 @@ 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 4e67310773..cc83f84d13 100644 --- a/judge/bridge/server.py +++ b/judge/bridge/server.py @@ -12,9 +12,7 @@ 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 83fc89ace2..21481394ff 100644 --- a/judge/comments.py +++ b/judge/comments.py @@ -7,12 +7,7 @@ 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 @@ -37,11 +32,8 @@ 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 @@ -54,11 +46,7 @@ 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() @@ -71,9 +59,8 @@ 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): @@ -95,11 +82,8 @@ 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) @@ -107,9 +91,7 @@ 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() @@ -120,14 +102,10 @@ 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) @@ -139,13 +117,9 @@ 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 8bd7d1de9d..9585eee1bf 100644 --- a/judge/contest_format/atcoder.py +++ b/judge/contest_format/atcoder.py @@ -29,9 +29,7 @@ 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: @@ -39,9 +37,7 @@ 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() @@ -55,8 +51,7 @@ 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 @@ -67,9 +62,7 @@ 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) @@ -78,13 +71,9 @@ 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 @@ -109,35 +98,14 @@ 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 1a14af0994..1cf3e9861c 100644 --- a/judge/contest_format/default.py +++ b/judge/contest_format/default.py @@ -20,9 +20,7 @@ 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) @@ -33,16 +31,12 @@ 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) @@ -56,25 +50,10 @@ 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'), ) @@ -84,25 +63,18 @@ 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 b7139b3ec9..93c245c9c1 100644 --- a/judge/contest_format/ecoo.py +++ b/judge/contest_format/ecoo.py @@ -17,11 +17,7 @@ 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. @@ -35,9 +31,7 @@ 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: @@ -45,9 +39,7 @@ 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() @@ -59,19 +51,18 @@ 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], ), @@ -92,17 +83,9 @@ 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']: @@ -118,35 +101,15 @@ 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'), @@ -157,20 +120,14 @@ 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: @@ -187,8 +144,6 @@ 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 c470c98e86..13dfe2ed7c 100644 --- a/judge/contest_format/icpc.py +++ b/judge/contest_format/icpc.py @@ -29,9 +29,7 @@ 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: @@ -39,9 +37,7 @@ 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() @@ -56,8 +52,7 @@ 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 @@ -68,9 +63,7 @@ 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) @@ -79,13 +72,9 @@ 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 @@ -111,35 +100,14 @@ 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'), @@ -166,7 +134,5 @@ 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 ba74fc8147..9cb75fa6fe 100644 --- a/judge/contest_format/ioi.py +++ b/judge/contest_format/ioi.py @@ -20,8 +20,7 @@ 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 @@ -65,9 +64,7 @@ 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) @@ -80,9 +77,7 @@ 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'] @@ -101,9 +96,7 @@ 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 d955ccf7e9..0b80b8c2d8 100644 --- a/judge/contest_format/legacy_ioi.py +++ b/judge/contest_format/legacy_ioi.py @@ -27,9 +27,7 @@ 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: @@ -47,18 +45,12 @@ 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']: @@ -82,29 +74,12 @@ 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('') @@ -112,23 +87,17 @@ 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 c71ead228e..d4d518424c 100644 --- a/judge/dblock.py +++ b/judge/dblock.py @@ -5,12 +5,10 @@ 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 d2e7c67dc4..29100bd993 100644 --- a/judge/event_poster.py +++ b/judge/event_poster.py @@ -10,12 +10,9 @@ 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 4d492112ec..74f6331e11 100644 --- a/judge/event_poster_amqp.py +++ b/judge/event_poster_amqp.py @@ -15,19 +15,14 @@ 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 f7035c8209..fba4052631 100644 --- a/judge/event_poster_ws.py +++ b/judge/event_poster_ws.py @@ -20,18 +20,14 @@ 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 fd865b6d6f..4e7cc3c641 100644 --- a/judge/feed.py +++ b/judge/feed.py @@ -12,9 +12,7 @@ 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] @@ -77,9 +75,7 @@ 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 7fd078a5bf..87003e56f2 100644 --- a/judge/forms.py +++ b/judge/forms.py @@ -10,36 +10,17 @@ 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 @@ -47,22 +28,15 @@ 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.'), }, @@ -71,24 +45,12 @@ 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'}), @@ -109,11 +71,7 @@ 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): @@ -121,13 +79,9 @@ 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 @@ -140,9 +94,7 @@ 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): @@ -168,16 +120,11 @@ 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:'), @@ -201,18 +148,14 @@ 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( @@ -231,9 +174,7 @@ 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): @@ -247,9 +188,8 @@ 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): @@ -262,9 +202,7 @@ 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') @@ -290,9 +228,7 @@ 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']) @@ -306,9 +242,7 @@ 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.')) @@ -317,9 +251,7 @@ 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.')) @@ -342,37 +274,24 @@ 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'] @@ -382,10 +301,7 @@ 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 0ee32ea47d..5b9f7d3d09 100644 --- a/judge/fulltext.py +++ b/judge/fulltext.py @@ -25,26 +25,20 @@ 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 056a91ff11..06a947ed97 100644 --- a/judge/highlight_code.py +++ b/judge/highlight_code.py @@ -14,12 +14,9 @@ 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) @@ -27,9 +24,5 @@ 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 6389bc2a3e..fa556db911 100644 --- a/judge/jinja2/__init__.py +++ b/judge/jinja2/__init__.py @@ -8,22 +8,8 @@ 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 039430949b..60b3f9753c 100644 --- a/judge/jinja2/datetime.py +++ b/judge/jinja2/datetime.py @@ -27,8 +27,6 @@ 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 e6352618e5..7b27fdebb7 100644 --- a/judge/jinja2/filesize.py +++ b/judge/jinja2/filesize.py @@ -28,11 +28,7 @@ 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 638e79a3a3..259bf3ef59 100644 --- a/judge/jinja2/gravatar.py +++ b/judge/jinja2/gravatar.py @@ -17,11 +17,7 @@ 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 c01f8f9e47..6bb8d689b3 100644 --- a/judge/jinja2/markdown/__init__.py +++ b/judge/jinja2/markdown/__init__.py @@ -71,12 +71,7 @@ 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: @@ -85,29 +80,23 @@ 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: @@ -115,9 +104,7 @@ def block_html(self, html): style += ['text-align: center'] return '<%s style="%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): @@ -133,9 +120,7 @@ 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 @@ -149,13 +134,9 @@ 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 @@ -180,7 +161,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 @@ -198,19 +179,10 @@ 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 560f03f2d7..3aed9d9730 100644 --- a/judge/jinja2/markdown/bleach_whitelist.py +++ b/judge/jinja2/markdown/bleach_whitelist.py @@ -5,1358 +5,347 @@ # 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', - '