Skip to content

Commit

Permalink
Merge branch 'master' into change-site-logo-from-ui
Browse files Browse the repository at this point in the history
  • Loading branch information
hieplpvip authored Sep 29, 2024
2 parents 763b94c + 50f7271 commit 8d887d5
Show file tree
Hide file tree
Showing 124 changed files with 4,296 additions and 2,263 deletions.
16 changes: 8 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install flake8
Expand All @@ -18,13 +18,13 @@ jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Cache pip
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-${{ runner.os }}-${{ secrets.CACHE_VERSION }}-${{ hashFiles('requirements.txt') }}
Expand All @@ -43,11 +43,11 @@ jobs:
styles:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: '18.x'
node-version: '20.x'
- name: Install npm packages
run: npm ci
- name: Build style.css
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/caniuse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ jobs:
if: github.repository == 'DMOJ/online-judge'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Download Can I use... data
run: |
curl -s https://raw.githubusercontent.com/Fyrd/caniuse/master/data.json | python3 -m json.tool > resources/caniuse.json
- name: Create pull request
uses: peter-evans/create-pull-request@v4
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.REPO_SCOPED_TOKEN }}
author: dmoj-build <build@dmoj.ca>
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/compilemessages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ jobs:
compilemessages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Checkout submodules
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/updatemessages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ jobs:
if: github.repository == 'DMOJ/online-judge'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python 3.11
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Checkout submodules
Expand Down Expand Up @@ -87,7 +87,7 @@ jobs:
git reset --hard "$i18n_head"
fi
- name: Create pull request
uses: peter-evans/create-pull-request@v4
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.REPO_SCOPED_TOKEN }}
author: dmoj-build <build@dmoj.ca>
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
path = resources/libs
url = https://github.com/DMOJ/site-assets.git
branch = master
[submodule "resources/vnoj"]
path = resources/vnoj
url = https://github.com/VNOI-Admin/vnoj-static.git
2 changes: 2 additions & 0 deletions additional_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
uwsgi
websocket-client
watchdog
matplotlib
12 changes: 12 additions & 0 deletions dmoj/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import socket

from celery import Celery
from celery.schedules import crontab
from celery.signals import task_failure

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

# Load periodic tasks
app.conf.beat_schedule = {
'daily-queue-time-stats': {
'task': 'judge.tasks.webhook.queue_time_stats',
'schedule': crontab(minute=0, hour=0),
'options': {
'expires': 60 * 60 * 24,
},
},
}


@task_failure.connect()
def celery_failure_log(sender, task_id, exception, traceback, *args, **kwargs):
Expand Down
37 changes: 31 additions & 6 deletions dmoj/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,25 @@

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

VNOJ_SHOULD_BAN_FOR_CHEATING_IN_CONTESTS = False
VNOJ_CONTEST_CHEATING_BAN_MESSAGE = 'Banned for multiple cheating offenses during contests'
VNOJ_MAX_DISQUALIFICATIONS_BEFORE_BANNING = 3

# List of subdomain that will be ignored in organization subdomain middleware
VNOJ_IGNORED_ORGANIZATION_SUBDOMAINS = ['oj', 'www', 'localhost']

# Enable organization credit system, if true, org will not be able to submit submissions
# if they run out of credit
VNOJ_ENABLE_ORGANIZATION_CREDIT_LIMITATION = False
# 3 hours free per month
VNOJ_MONTHLY_FREE_CREDIT = 3 * 60 * 60
VNOJ_PRICE_PER_HOUR = 50


VNOJ_LONG_QUEUE_ALERT_THRESHOLD = 10

CELERY_TIMEZONE = 'Asia/Ho_Chi_Minh'

# Some problems have a lot of testcases, and each testcase
# has about 5~6 fields, so we need to raise this
DATA_UPLOAD_MAX_NUMBER_FIELDS = 3000
Expand Down Expand Up @@ -165,13 +181,15 @@
'on_new_tag': None,
'on_new_blogpost': None,
'on_error': None,
'on_long_queue': None,
'queue_time_stats': None,
}

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

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'
SELECT2_CSS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css'
ACE_URL = '/static/vnoj/ace/1.4.14'
SELECT2_JS_URL = '/static/vnoj/select2/4.0.3/js/select2.min.js'
SELECT2_CSS_URL = '/static/vnoj/select2/4.0.3/css/select2.min.css'

DMOJ_CAMO_URL = None
DMOJ_CAMO_KEY = None
Expand Down Expand Up @@ -277,8 +295,8 @@

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'
JQUERY_JS = '/static/vnoj/jquery/3.4.1/jquery.min.js'
FONTAWESOME_CSS = '/static/vnoj/font-awesome/4.3.0/css/font-awesome.min.css'
DMOJ_CANONICAL = 'oj.vnoi.info'

# Application definition
Expand Down Expand Up @@ -645,6 +663,9 @@

ENABLE_FTS = False

# Balancer configuration
BALANCER_JUDGE_ADDRESS = [('localhost', 8888)]

# Bridged configuration
BRIDGED_JUDGE_ADDRESS = [('localhost', 9999)]
BRIDGED_JUDGE_PROXIES = None
Expand Down Expand Up @@ -697,6 +718,7 @@
'social_core.backends.facebook.FacebookOAuth2',
'judge.social_auth.GitHubSecureEmailOAuth2',
'django.contrib.auth.backends.ModelBackend',
'judge.ip_auth.IPBasedAuthBackend',
)

SOCIAL_AUTH_PIPELINE = (
Expand All @@ -707,8 +729,9 @@
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.social_auth.associate_by_email',
'judge.social_auth.choose_username',
'judge.social_auth.get_username_password',
'social_core.pipeline.user.create_user',
'judge.social_auth.add_password',
'judge.social_auth.make_profile',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
Expand All @@ -721,6 +744,8 @@
SOCIAL_AUTH_SLUGIFY_FUNCTION = 'judge.social_auth.slugify_username'
SOCIAL_AUTH_PROTECTED_USER_FIELDS = ['first_name', 'last_name']

IP_BASED_AUTHENTICATION_HEADER = 'REMOTE_ADDR'

MOSS_API_KEY = None

CELERY_WORKER_HIJACK_ROOT_LOGGER = False
Expand Down
6 changes: 6 additions & 0 deletions dmoj/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from judge.views import TitledTemplateView, api, blog, comment, contests, language, license, mailgun, organization, \
preview, problem, problem_manage, ranked_submission, register, stats, status, submission, tag, tasks, ticket, \
two_factor, user, widgets
from judge.views.magazine import MagazinePage
from judge.views.misc_config import MiscConfigEdit
from judge.views.problem_data import ProblemDataView, ProblemSubmissionDiff, \
problem_data_file, problem_init_view
Expand Down Expand Up @@ -127,6 +128,7 @@ def paged_list_view(view, name):
path('/clone', problem.ProblemClone.as_view(), name='problem_clone'),
path('/submit', problem.ProblemSubmit.as_view(), name='problem_submit'),
path('/resubmit/<int:submission>', problem.ProblemSubmit.as_view(), name='problem_submit'),
path('/update-polygon', problem.ProblemUpdatePolygon.as_view(), name='problem_update_polygon'),

path('/rank/', paged_list_view(ranked_submission.RankedSubmissions, 'ranked_submissions')),
path('/submissions/', paged_list_view(submission.ProblemSubmissions, 'chronological_submissions')),
Expand Down Expand Up @@ -281,11 +283,13 @@ def paged_list_view(view, name):
lambda _, pk, suffix: HttpResponsePermanentRedirect('/organization/%s' % suffix)),
path('organization/<slug:slug>', include([
path('', organization.OrganizationHome.as_view(), name='organization_home'),
path('/<int:page>', 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('/usage', organization.MonthlyCreditUsageOrganization.as_view(), name='organization_monthly_usage'),
path('/problems/', organization.ProblemListOrganization.as_view(), name='problem_list_organization'),
path('/contests/', organization.ContestListOrganization.as_view(), name='contest_list_organization'),
path('/submissions/',
Expand Down Expand Up @@ -417,6 +421,8 @@ def paged_list_view(view, name):
path('progress', tasks.demo_progress),
])),

path('magazine/', MagazinePage.as_view(), name='magazine'),

path('misc_config/', MiscConfigEdit.as_view(), name='misc_config'),
]

Expand Down
28 changes: 23 additions & 5 deletions judge/admin/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
from django.shortcuts import get_object_or_404
from django.urls import path, reverse, reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _, ngettext
from django.views.decorators.http import require_POST
from reversion.admin import VersionAdmin

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

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


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


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

@method_decorator(require_POST)
def rejudge_view(self, request, contest_id, problem_id):
queryset = ContestSubmission.objects.filter(problem_id=problem_id).select_related('submission')
contest = get_object_or_404(Contest, id=contest_id)
if not request.user.is_staff or not self.has_change_permission(request, contest):
raise PermissionDenied()
queryset = ContestSubmission.objects.filter(participation__contest_id=contest_id,
problem_id=problem_id).select_related('submission')
for model in queryset:
model.submission.judge(rejudge=True, rejudge_user=request.user)

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

@method_decorator(require_POST)
def rescore_view(self, request, contest_id, problem_id):
queryset = ContestSubmission.objects.filter(problem_id=problem_id).select_related('submission')
contest = get_object_or_404(Contest, id=contest_id)
if not request.user.is_staff or not self.has_change_permission(request, contest):
raise PermissionDenied()
queryset = ContestSubmission.objects.filter(participation__contest_id=contest_id,
problem_id=problem_id).select_related('submission')
for model in queryset:
model.submission.update_contest()

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

@method_decorator(require_POST)
def resend_view(self, request, contest_id, announcement_id):
contest = get_object_or_404(Contest, id=contest_id)
if not request.user.is_staff or not self.has_change_permission(request, contest):
raise PermissionDenied()
announcement = get_object_or_404(ContestAnnouncement, id=announcement_id)
announcement.send()
return HttpResponseRedirect(reverse('admin:judge_contest_change', args=(contest_id,)))

@method_decorator(require_POST)
def rate_all_view(self, request):
if not request.user.has_perm('judge.contest_rating'):
raise PermissionDenied()
Expand All @@ -330,6 +347,7 @@ def rate_all_view(self, request):
rate_contest(contest)
return HttpResponseRedirect(reverse('admin:judge_contest_changelist'))

@method_decorator(require_POST)
def rate_view(self, request, id):
if not request.user.has_perm('judge.contest_rating'):
raise PermissionDenied()
Expand Down
6 changes: 5 additions & 1 deletion judge/admin/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class BlogPostAdmin(VersionAdmin):
(_('Summary'), {'classes': ('collapse',), 'fields': ('summary',)}),
)
prepopulated_fields = {'slug': ('title',)}
list_display = ('id', 'title', 'visible', 'global_post', 'sticky', 'publish_on')
list_display = ('id', 'title', 'show_authors', 'visible', 'global_post', 'sticky', 'publish_on')
list_display_links = ('id', 'title')
ordering = ('-publish_on',)
form = BlogPostForm
Expand All @@ -89,6 +89,10 @@ def has_change_permission(self, request, obj=None):
return request.user.has_perm('judge.change_blogpost')
return obj.is_editable_by(request.user)

@admin.display(description=_('authors'))
def show_authors(self, obj):
return ', '.join(map(str, obj.authors.all()))


class SolutionForm(ModelForm):
def __init__(self, *args, **kwargs):
Expand Down
6 changes: 3 additions & 3 deletions judge/admin/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ class Meta:


class OrganizationAdmin(VersionAdmin):
readonly_fields = ('creation_date',)
fields = ('name', 'slug', 'short_name', 'is_open', 'is_unlisted', 'about', 'logo_override_image', 'slots',
'creation_date', 'admins')
readonly_fields = ('creation_date', 'current_consumed_credit')
fields = ('name', 'slug', 'short_name', 'is_open', 'is_unlisted', 'available_credit', 'current_consumed_credit',
'about', 'logo_override_image', 'slots', 'creation_date', 'admins')
list_display = ('name', 'short_name', 'is_open', 'is_unlisted', 'slots', 'show_public')
prepopulated_fields = {'slug': ('name',)}
actions = ('recalculate_points',)
Expand Down
Loading

0 comments on commit 8d887d5

Please sign in to comment.