Skip to content

Commit

Permalink
feat: merge VNOI
Browse files Browse the repository at this point in the history
  • Loading branch information
LLaammTTeerr committed Aug 10, 2024
1 parent ea285dd commit bb88aa8
Show file tree
Hide file tree
Showing 57 changed files with 1,151 additions and 68 deletions.
4 changes: 2 additions & 2 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ exclude =
./dmoj/local_urls.py,
# is actually a fragment to be included by settings.py
./.ci.settings.py,
# ignore migration files
./judge/migrations/*.py,
# ignore migrations files
./judge/migrations/*.py
fc_*
1 change: 0 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[submodule "resources/libs"]
path = resources/libs
url = https://github.com/qhhoj/site-assets.git
branch = master
12 changes: 12 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.DS_Store
node_modules
/build
/package
.env
.env.*
!.env.example

# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
9 changes: 9 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"semi": true,
"useTabs": false,
"tabWidth": 2,
"printWidth": 100,
"singleQuote": false,
"trailingComma": "all",
"bracketSameLine": false
}
16 changes: 12 additions & 4 deletions dmoj/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
VNOJ_CONTEST_DURATION_LIMIT = 14
# Maximum number of test cases that a user can create for a problem
# without the `create_mass_testcases` permission
VNOJ_TESTCASE_HARD_LIMIT = 300
VNOJ_TESTCASE_HARD_LIMIT = 100
# If a user without the `create_mass_testcases` permission create more than this amount of test
# they will receive a warning
VNOJ_TESTCASE_SOFT_LIMIT = 50
Expand All @@ -100,6 +100,10 @@

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']

Expand Down Expand Up @@ -149,7 +153,7 @@
('VNOJ', 'VNOJ'),
]

OJ_REQUESTS_TIMEOUT = 30 # in seconds
OJ_REQUESTS_TIMEOUT = 5 # in seconds

OJAPI_CACHE_TIMEOUT = 3600 # Cache timeout for OJAPI data

Expand All @@ -167,7 +171,7 @@
'on_error': None,
}

SITE_FULL_URL = 'https://qhhoj.com'
SITE_FULL_URL = 'https://qhhoj.com' # 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'
Expand Down Expand Up @@ -702,6 +706,7 @@
'social_core.backends.facebook.FacebookOAuth2',
'judge.social_auth.GitHubSecureEmailOAuth2',
'django.contrib.auth.backends.ModelBackend',
'judge.ip_auth.IPBasedAuthBackend',
)

SOCIAL_AUTH_PIPELINE = (
Expand All @@ -712,8 +717,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 @@ -726,6 +732,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
3 changes: 3 additions & 0 deletions dmoj/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
two_factor, user, widgets
from judge.views.URL import redirect_url, shorten_url
from judge.views.flatpages import FlatPageViewGenerator
from judge.views.magazine import MagazinePage
from judge.views.problem_data import ProblemDataView, ProblemSubmissionDiff, \
problem_data_file, problem_init_view
from judge.views.register import ActivationView, RegistrationView
Expand Down Expand Up @@ -288,6 +289,7 @@ 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'),
Expand Down Expand Up @@ -429,6 +431,7 @@ def paged_list_view(view, name):
path('', shorten_url, name='shorten_url'),
path('<str:short_code>/', redirect_url, name='redirect_url'),
])),
path('magazine/', MagazinePage.as_view(), name='magazine'),
]

favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png',
Expand Down
27 changes: 21 additions & 6 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,11 +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):
contest = get_object_or_404(Contest, id=contest_id)
if not self.has_change_permission(request, contest):
if not request.user.is_staff or not self.has_change_permission(request, contest):
raise PermissionDenied()
queryset = ContestSubmission.objects.filter(problem_id=problem_id).select_related('submission')
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 @@ -307,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 @@ -317,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 @@ -333,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
4 changes: 4 additions & 0 deletions judge/admin/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 4 additions & 3 deletions judge/admin/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ def has_add_permission(self, request, obj=None):


class ProfileAdmin(NoBatchDeleteMixin, VersionAdmin):
fields = ('user', 'display_rank', 'badges', 'display_badge', 'about', 'organizations', 'timezone', 'language',
'ace_theme', 'math_engine', 'last_access', 'ip', 'mute', 'is_unlisted', 'allow_tagging', 'notes',
'username_display_override', 'ban_reason', 'is_totp_enabled', 'user_script', 'current_contest')
fields = ('user', 'display_rank', 'badges', 'display_badge', 'about', 'organizations', 'vnoj_points', 'timezone',
'language', 'ace_theme', 'math_engine', 'last_access', 'ip', 'mute', 'is_unlisted', 'allow_tagging',
'notes', 'username_display_override', 'ban_reason', 'is_totp_enabled', 'ip_auth', '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')
Expand Down
5 changes: 5 additions & 0 deletions judge/admin/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import path, reverse
from django.utils.decorators import method_decorator
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_POST
from reversion.admin import VersionAdmin

from django_ace import AceWidget
Expand Down Expand Up @@ -93,18 +95,21 @@ def disconnect_judge(self, id, force=False):
judge.disconnect(force=force)
return HttpResponseRedirect(reverse('admin:judge_judge_changelist'))

@method_decorator(require_POST)
def disconnect_view(self, request, id):
judge = get_object_or_404(Judge, id=id)
if not self.has_change_permission(request, judge):
raise PermissionDenied()
return self.disconnect_judge(id)

@method_decorator(require_POST)
def terminate_view(self, request, id):
judge = get_object_or_404(Judge, id=id)
if not self.has_change_permission(request, judge):
raise PermissionDenied()
return self.disconnect_judge(id, force=True)

@method_decorator(require_POST)
def disable_view(self, request, id):
judge = get_object_or_404(Judge, id=id)
if not self.has_change_permission(request, judge):
Expand Down
3 changes: 3 additions & 0 deletions judge/admin/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import path, reverse
from django.utils.decorators import method_decorator
from django.utils.html import format_html
from django.utils.translation import gettext, gettext_lazy as _, ngettext, pgettext
from django.views.decorators.http import require_POST
from reversion.admin import VersionAdmin

from django_ace import AceWidget
Expand Down Expand Up @@ -252,6 +254,7 @@ def get_urls(self):
path('<int:id>/judge/', self.judge_view, name='judge_submission_rejudge'),
] + super(SubmissionAdmin, self).get_urls()

@method_decorator(require_POST)
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'):
raise PermissionDenied()
Expand Down
3 changes: 3 additions & 0 deletions judge/bridge/judge_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ def __init__(self, request, client_address, server, judges):
self.judge = None
self.judge_address = None

self._submission_cache_id = None
self._submission_cache = {}

def on_connect(self):
self.timeout = 15
logger.info('Judge connected from: %s', self.client_address)
Expand Down
1 change: 0 additions & 1 deletion judge/contest_format/ioi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from judge.contest_format.legacy_ioi import LegacyIOIContestFormat
from judge.contest_format.registry import register_contest_format
# from judge.timezone import from_database_time


@register_contest_format('ioi16')
Expand Down
13 changes: 13 additions & 0 deletions judge/ip_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.contrib.auth.backends import ModelBackend

from judge.models import Profile


class IPBasedAuthBackend(ModelBackend):
def authenticate(self, request, ip_auth):
user = None
try:
user = Profile.objects.select_related('user').get(ip_auth=ip_auth).user
except Profile.DoesNotExist:
pass
return user if self.user_can_authenticate(user) else None
2 changes: 1 addition & 1 deletion judge/management/commands/batchadduser.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def add_user(username, fullname, password):
usr.set_password(password)
usr.save()

profile = Profile(user=usr, is_unlisted=True)
profile = Profile(user=usr)
profile.language = Language.objects.get(key=settings.DEFAULT_USER_LANGUAGE)
profile.save()

Expand Down
20 changes: 18 additions & 2 deletions judge/management/commands/submit_polygon_solutions.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,38 @@ def handle(self, *args, **options):
if source_lang.startswith('cpp'):
header = '/*\n' + '\n'.join(comments) + '\n*/\n\n'
source_code = header + package.read(source_path).decode('utf-8')
language = Language.objects.get(key='CPP17')
language = Language.objects.get(key='CPP20')
elif source_lang.startswith('java'):
header = '/*\n' + '\n'.join(comments) + '\n*/\n\n'
source_code = header + package.read(source_path).decode('utf-8')
language = Language.objects.get(key='JAVA')
elif source_lang.startswith('pas'):
header = '{\n' + '\n'.join(comments) + '\n}\n\n'
source_code = header + package.read(source_path).decode('utf-8')
language = Language.objects.get(key='PAS')
elif source_lang.startswith('python'):
header = '"""\n' + '\n'.join(comments) + '\n"""\n\n'
source_code = header + package.read(source_path).decode('utf-8')
if source_lang == 'python.pypy2':
language = Language.objects.get(key='PYPY')
elif source_lang == 'python.pypy3':
elif source_lang.startswith('python.pypy3'):
language = Language.objects.get(key='PYPY3')
elif source_lang == 'python.2':
language = Language.objects.get(key='PY2')
else:
language = Language.objects.get(key='PY3')
elif source_lang.startswith('kotlin'):
header = '/*\n' + '\n'.join(comments) + '\n*/\n\n'
source_code = header + package.read(source_path).decode('utf-8')
language = Language.objects.get(key='KOTLIN')
elif source_lang == 'go':
header = '/*\n' + '\n'.join(comments) + '\n*/\n\n'
source_code = header + package.read(source_path).decode('utf-8')
language = Language.objects.get(key='GO')
elif source_lang == 'rust':
header = '/*\n' + '\n'.join(comments) + '\n*/\n\n'
source_code = header + package.read(source_path).decode('utf-8')
language = Language.objects.get(key='RUST')
else:
print('Unsupported language', source_lang)
continue
Expand Down
Loading

0 comments on commit bb88aa8

Please sign in to comment.