Skip to content

Commit 8d887d5

Browse files
authored
Merge branch 'master' into change-site-logo-from-ui
2 parents 763b94c + 50f7271 commit 8d887d5

File tree

124 files changed

+4296
-2263
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+4296
-2263
lines changed

.github/workflows/build.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ jobs:
44
lint:
55
runs-on: ubuntu-latest
66
steps:
7-
- uses: actions/checkout@v3
7+
- uses: actions/checkout@v4
88
- name: Set up Python 3.11
9-
uses: actions/setup-python@v4
9+
uses: actions/setup-python@v5
1010
with:
1111
python-version: '3.11'
1212
- name: Install flake8
@@ -18,13 +18,13 @@ jobs:
1818
unit:
1919
runs-on: ubuntu-latest
2020
steps:
21-
- uses: actions/checkout@v3
21+
- uses: actions/checkout@v4
2222
- name: Set up Python 3.11
23-
uses: actions/setup-python@v4
23+
uses: actions/setup-python@v5
2424
with:
2525
python-version: '3.11'
2626
- name: Cache pip
27-
uses: actions/cache@v2
27+
uses: actions/cache@v4
2828
with:
2929
path: ~/.cache/pip
3030
key: pip-${{ runner.os }}-${{ secrets.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}
@@ -43,11 +43,11 @@ jobs:
4343
styles:
4444
runs-on: ubuntu-latest
4545
steps:
46-
- uses: actions/checkout@v3
46+
- uses: actions/checkout@v4
4747
- name: Install Node.js
48-
uses: actions/setup-node@v3
48+
uses: actions/setup-node@v4
4949
with:
50-
node-version: '18.x'
50+
node-version: '20.x'
5151
- name: Install npm packages
5252
run: npm ci
5353
- name: Build style.css

.github/workflows/caniuse.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ jobs:
77
if: github.repository == 'DMOJ/online-judge'
88
runs-on: ubuntu-latest
99
steps:
10-
- uses: actions/checkout@v3
10+
- uses: actions/checkout@v4
1111
- name: Download Can I use... data
1212
run: |
1313
curl -s https://raw.githubusercontent.com/Fyrd/caniuse/master/data.json | python3 -m json.tool > resources/caniuse.json
1414
- name: Create pull request
15-
uses: peter-evans/create-pull-request@v4
15+
uses: peter-evans/create-pull-request@v6
1616
with:
1717
token: ${{ secrets.REPO_SCOPED_TOKEN }}
1818
author: dmoj-build <[email protected]>

.github/workflows/compilemessages.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ jobs:
1010
compilemessages:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v3
13+
- uses: actions/checkout@v4
1414
- name: Set up Python 3.11
15-
uses: actions/setup-python@v4
15+
uses: actions/setup-python@v5
1616
with:
1717
python-version: '3.11'
1818
- name: Checkout submodules

.github/workflows/updatemessages.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ jobs:
77
if: github.repository == 'DMOJ/online-judge'
88
runs-on: ubuntu-latest
99
steps:
10-
- uses: actions/checkout@v3
10+
- uses: actions/checkout@v4
1111
with:
1212
fetch-depth: 0
1313
- name: Set up Python 3.11
14-
uses: actions/setup-python@v4
14+
uses: actions/setup-python@v5
1515
with:
1616
python-version: '3.11'
1717
- name: Checkout submodules
@@ -87,7 +87,7 @@ jobs:
8787
git reset --hard "$i18n_head"
8888
fi
8989
- name: Create pull request
90-
uses: peter-evans/create-pull-request@v4
90+
uses: peter-evans/create-pull-request@v6
9191
with:
9292
token: ${{ secrets.REPO_SCOPED_TOKEN }}
9393
author: dmoj-build <[email protected]>

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
path = resources/libs
33
url = https://github.com/DMOJ/site-assets.git
44
branch = master
5+
[submodule "resources/vnoj"]
6+
path = resources/vnoj
7+
url = https://github.com/VNOI-Admin/vnoj-static.git

additional_requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
uwsgi
22
websocket-client
3+
watchdog
4+
matplotlib

dmoj/celery.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import socket
33

44
from celery import Celery
5+
from celery.schedules import crontab
56
from celery.signals import task_failure
67

78
app = Celery('dmoj')
@@ -20,6 +21,17 @@
2021
# Logger to enable reporting of errors.
2122
logger = logging.getLogger('judge.celery')
2223

24+
# Load periodic tasks
25+
app.conf.beat_schedule = {
26+
'daily-queue-time-stats': {
27+
'task': 'judge.tasks.webhook.queue_time_stats',
28+
'schedule': crontab(minute=0, hour=0),
29+
'options': {
30+
'expires': 60 * 60 * 24,
31+
},
32+
},
33+
}
34+
2335

2436
@task_failure.connect()
2537
def celery_failure_log(sender, task_id, exception, traceback, *args, **kwargs):

dmoj/settings.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,25 @@
100100

101101
VNOJ_TAG_PROBLEM_MIN_RATING = 1900 # Minimum rating to be able to tag a problem
102102

103+
VNOJ_SHOULD_BAN_FOR_CHEATING_IN_CONTESTS = False
104+
VNOJ_CONTEST_CHEATING_BAN_MESSAGE = 'Banned for multiple cheating offenses during contests'
105+
VNOJ_MAX_DISQUALIFICATIONS_BEFORE_BANNING = 3
106+
103107
# List of subdomain that will be ignored in organization subdomain middleware
104108
VNOJ_IGNORED_ORGANIZATION_SUBDOMAINS = ['oj', 'www', 'localhost']
105109

110+
# Enable organization credit system, if true, org will not be able to submit submissions
111+
# if they run out of credit
112+
VNOJ_ENABLE_ORGANIZATION_CREDIT_LIMITATION = False
113+
# 3 hours free per month
114+
VNOJ_MONTHLY_FREE_CREDIT = 3 * 60 * 60
115+
VNOJ_PRICE_PER_HOUR = 50
116+
117+
118+
VNOJ_LONG_QUEUE_ALERT_THRESHOLD = 10
119+
120+
CELERY_TIMEZONE = 'Asia/Ho_Chi_Minh'
121+
106122
# Some problems have a lot of testcases, and each testcase
107123
# has about 5~6 fields, so we need to raise this
108124
DATA_UPLOAD_MAX_NUMBER_FIELDS = 3000
@@ -165,13 +181,15 @@
165181
'on_new_tag': None,
166182
'on_new_blogpost': None,
167183
'on_error': None,
184+
'on_long_queue': None,
185+
'queue_time_stats': None,
168186
}
169187

170188
SITE_FULL_URL = None # ie 'https://oj.vnoi.info', please remove the last / if needed
171189

172-
ACE_URL = '//cdnjs.cloudflare.com/ajax/libs/ace/1.1.3'
173-
SELECT2_JS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js'
174-
SELECT2_CSS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css'
190+
ACE_URL = '/static/vnoj/ace/1.4.14'
191+
SELECT2_JS_URL = '/static/vnoj/select2/4.0.3/js/select2.min.js'
192+
SELECT2_CSS_URL = '/static/vnoj/select2/4.0.3/css/select2.min.css'
175193

176194
DMOJ_CAMO_URL = None
177195
DMOJ_CAMO_KEY = None
@@ -277,8 +295,8 @@
277295

278296
INLINE_JQUERY = True
279297
INLINE_FONTAWESOME = True
280-
JQUERY_JS = '//ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js'
281-
FONTAWESOME_CSS = '//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css'
298+
JQUERY_JS = '/static/vnoj/jquery/3.4.1/jquery.min.js'
299+
FONTAWESOME_CSS = '/static/vnoj/font-awesome/4.3.0/css/font-awesome.min.css'
282300
DMOJ_CANONICAL = 'oj.vnoi.info'
283301

284302
# Application definition
@@ -645,6 +663,9 @@
645663

646664
ENABLE_FTS = False
647665

666+
# Balancer configuration
667+
BALANCER_JUDGE_ADDRESS = [('localhost', 8888)]
668+
648669
# Bridged configuration
649670
BRIDGED_JUDGE_ADDRESS = [('localhost', 9999)]
650671
BRIDGED_JUDGE_PROXIES = None
@@ -697,6 +718,7 @@
697718
'social_core.backends.facebook.FacebookOAuth2',
698719
'judge.social_auth.GitHubSecureEmailOAuth2',
699720
'django.contrib.auth.backends.ModelBackend',
721+
'judge.ip_auth.IPBasedAuthBackend',
700722
)
701723

702724
SOCIAL_AUTH_PIPELINE = (
@@ -707,8 +729,9 @@
707729
'social_core.pipeline.social_auth.social_user',
708730
'social_core.pipeline.user.get_username',
709731
'social_core.pipeline.social_auth.associate_by_email',
710-
'judge.social_auth.choose_username',
732+
'judge.social_auth.get_username_password',
711733
'social_core.pipeline.user.create_user',
734+
'judge.social_auth.add_password',
712735
'judge.social_auth.make_profile',
713736
'social_core.pipeline.social_auth.associate_user',
714737
'social_core.pipeline.social_auth.load_extra_data',
@@ -721,6 +744,8 @@
721744
SOCIAL_AUTH_SLUGIFY_FUNCTION = 'judge.social_auth.slugify_username'
722745
SOCIAL_AUTH_PROTECTED_USER_FIELDS = ['first_name', 'last_name']
723746

747+
IP_BASED_AUTHENTICATION_HEADER = 'REMOTE_ADDR'
748+
724749
MOSS_API_KEY = None
725750

726751
CELERY_WORKER_HIJACK_ROOT_LOGGER = False

dmoj/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from judge.views import TitledTemplateView, api, blog, comment, contests, language, license, mailgun, organization, \
1919
preview, problem, problem_manage, ranked_submission, register, stats, status, submission, tag, tasks, ticket, \
2020
two_factor, user, widgets
21+
from judge.views.magazine import MagazinePage
2122
from judge.views.misc_config import MiscConfigEdit
2223
from judge.views.problem_data import ProblemDataView, ProblemSubmissionDiff, \
2324
problem_data_file, problem_init_view
@@ -127,6 +128,7 @@ def paged_list_view(view, name):
127128
path('/clone', problem.ProblemClone.as_view(), name='problem_clone'),
128129
path('/submit', problem.ProblemSubmit.as_view(), name='problem_submit'),
129130
path('/resubmit/<int:submission>', problem.ProblemSubmit.as_view(), name='problem_submit'),
131+
path('/update-polygon', problem.ProblemUpdatePolygon.as_view(), name='problem_update_polygon'),
130132

131133
path('/rank/', paged_list_view(ranked_submission.RankedSubmissions, 'ranked_submissions')),
132134
path('/submissions/', paged_list_view(submission.ProblemSubmissions, 'chronological_submissions')),
@@ -281,11 +283,13 @@ def paged_list_view(view, name):
281283
lambda _, pk, suffix: HttpResponsePermanentRedirect('/organization/%s' % suffix)),
282284
path('organization/<slug:slug>', include([
283285
path('', organization.OrganizationHome.as_view(), name='organization_home'),
286+
path('/<int:page>', organization.OrganizationHome.as_view(), name='organization_home'),
284287
path('/users/', organization.OrganizationUsers.as_view(), name='organization_users'),
285288
path('/join', organization.JoinOrganization.as_view(), name='join_organization'),
286289
path('/leave', organization.LeaveOrganization.as_view(), name='leave_organization'),
287290
path('/edit', organization.EditOrganization.as_view(), name='edit_organization'),
288291
path('/kick', organization.KickUserWidgetView.as_view(), name='organization_user_kick'),
292+
path('/usage', organization.MonthlyCreditUsageOrganization.as_view(), name='organization_monthly_usage'),
289293
path('/problems/', organization.ProblemListOrganization.as_view(), name='problem_list_organization'),
290294
path('/contests/', organization.ContestListOrganization.as_view(), name='contest_list_organization'),
291295
path('/submissions/',
@@ -417,6 +421,8 @@ def paged_list_view(view, name):
417421
path('progress', tasks.demo_progress),
418422
])),
419423

424+
path('magazine/', MagazinePage.as_view(), name='magazine'),
425+
420426
path('misc_config/', MiscConfigEdit.as_view(), name='misc_config'),
421427
]
422428

judge/admin/contest.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
from django.shortcuts import get_object_or_404
99
from django.urls import path, reverse, reverse_lazy
1010
from django.utils import timezone
11+
from django.utils.decorators import method_decorator
1112
from django.utils.html import format_html
1213
from django.utils.translation import gettext_lazy as _, ngettext
14+
from django.views.decorators.http import require_POST
1315
from reversion.admin import VersionAdmin
1416

1517
from django_ace import AceWidget
@@ -73,14 +75,14 @@ class ContestProblemInline(SortableInlineAdminMixin, admin.TabularInline):
7375
def rejudge_column(self, obj):
7476
if obj.id is None:
7577
return ''
76-
return format_html('<a class="button rejudge-link" href="{0}">{1}</a>',
78+
return format_html('<a class="button rejudge-link action-link" href="{0}">{1}</a>',
7779
reverse('admin:judge_contest_rejudge', args=(obj.contest.id, obj.id)), _('Rejudge'))
7880

7981
@admin.display(description='')
8082
def rescore_column(self, obj):
8183
if obj.id is None:
8284
return ''
83-
return format_html('<a class="button rescore-link" href="{}">Rescore</a>',
85+
return format_html('<a class="button rescore-link action-link" href="{}">Rescore</a>',
8486
reverse('admin:judge_contest_rescore', args=(obj.contest.id, obj.id)))
8587

8688

@@ -100,7 +102,7 @@ class ContestAnnouncementInline(admin.StackedInline):
100102
def resend(self, obj):
101103
if obj.id is None:
102104
return 'Not available'
103-
return format_html('<a class="button resend-link" href="{}">Resend</a>',
105+
return format_html('<a class="button resend-link action-link" href="{}">Resend</a>',
104106
reverse('admin:judge_contest_resend', args=(obj.contest.id, obj.id)))
105107

106108

@@ -294,8 +296,13 @@ def get_urls(self):
294296
path('<int:contest_id>/resend/<int:announcement_id>/', self.resend_view, name='judge_contest_resend'),
295297
] + super(ContestAdmin, self).get_urls()
296298

299+
@method_decorator(require_POST)
297300
def rejudge_view(self, request, contest_id, problem_id):
298-
queryset = ContestSubmission.objects.filter(problem_id=problem_id).select_related('submission')
301+
contest = get_object_or_404(Contest, id=contest_id)
302+
if not request.user.is_staff or not self.has_change_permission(request, contest):
303+
raise PermissionDenied()
304+
queryset = ContestSubmission.objects.filter(participation__contest_id=contest_id,
305+
problem_id=problem_id).select_related('submission')
299306
for model in queryset:
300307
model.submission.judge(rejudge=True, rejudge_user=request.user)
301308

@@ -304,8 +311,13 @@ def rejudge_view(self, request, contest_id, problem_id):
304311
len(queryset)) % len(queryset))
305312
return HttpResponseRedirect(reverse('admin:judge_contest_change', args=(contest_id,)))
306313

314+
@method_decorator(require_POST)
307315
def rescore_view(self, request, contest_id, problem_id):
308-
queryset = ContestSubmission.objects.filter(problem_id=problem_id).select_related('submission')
316+
contest = get_object_or_404(Contest, id=contest_id)
317+
if not request.user.is_staff or not self.has_change_permission(request, contest):
318+
raise PermissionDenied()
319+
queryset = ContestSubmission.objects.filter(participation__contest_id=contest_id,
320+
problem_id=problem_id).select_related('submission')
309321
for model in queryset:
310322
model.submission.update_contest()
311323

@@ -314,11 +326,16 @@ def rescore_view(self, request, contest_id, problem_id):
314326
len(queryset)) % len(queryset))
315327
return HttpResponseRedirect(reverse('admin:judge_contest_change', args=(contest_id,)))
316328

329+
@method_decorator(require_POST)
317330
def resend_view(self, request, contest_id, announcement_id):
331+
contest = get_object_or_404(Contest, id=contest_id)
332+
if not request.user.is_staff or not self.has_change_permission(request, contest):
333+
raise PermissionDenied()
318334
announcement = get_object_or_404(ContestAnnouncement, id=announcement_id)
319335
announcement.send()
320336
return HttpResponseRedirect(reverse('admin:judge_contest_change', args=(contest_id,)))
321337

338+
@method_decorator(require_POST)
322339
def rate_all_view(self, request):
323340
if not request.user.has_perm('judge.contest_rating'):
324341
raise PermissionDenied()
@@ -330,6 +347,7 @@ def rate_all_view(self, request):
330347
rate_contest(contest)
331348
return HttpResponseRedirect(reverse('admin:judge_contest_changelist'))
332349

350+
@method_decorator(require_POST)
333351
def rate_view(self, request, id):
334352
if not request.user.has_perm('judge.contest_rating'):
335353
raise PermissionDenied()

judge/admin/interface.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class BlogPostAdmin(VersionAdmin):
7878
(_('Summary'), {'classes': ('collapse',), 'fields': ('summary',)}),
7979
)
8080
prepopulated_fields = {'slug': ('title',)}
81-
list_display = ('id', 'title', 'visible', 'global_post', 'sticky', 'publish_on')
81+
list_display = ('id', 'title', 'show_authors', 'visible', 'global_post', 'sticky', 'publish_on')
8282
list_display_links = ('id', 'title')
8383
ordering = ('-publish_on',)
8484
form = BlogPostForm
@@ -89,6 +89,10 @@ def has_change_permission(self, request, obj=None):
8989
return request.user.has_perm('judge.change_blogpost')
9090
return obj.is_editable_by(request.user)
9191

92+
@admin.display(description=_('authors'))
93+
def show_authors(self, obj):
94+
return ', '.join(map(str, obj.authors.all()))
95+
9296

9397
class SolutionForm(ModelForm):
9498
def __init__(self, *args, **kwargs):

judge/admin/organization.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class Meta:
1818

1919

2020
class OrganizationAdmin(VersionAdmin):
21-
readonly_fields = ('creation_date',)
22-
fields = ('name', 'slug', 'short_name', 'is_open', 'is_unlisted', 'about', 'logo_override_image', 'slots',
23-
'creation_date', 'admins')
21+
readonly_fields = ('creation_date', 'current_consumed_credit')
22+
fields = ('name', 'slug', 'short_name', 'is_open', 'is_unlisted', 'available_credit', 'current_consumed_credit',
23+
'about', 'logo_override_image', 'slots', 'creation_date', 'admins')
2424
list_display = ('name', 'short_name', 'is_open', 'is_unlisted', 'slots', 'show_public')
2525
prepopulated_fields = {'slug': ('name',)}
2626
actions = ('recalculate_points',)

0 commit comments

Comments
 (0)