Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dmoj/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@
DMOJ_PROBLEM_MAX_TIME_LIMIT = 60 # seconds
DMOJ_PROBLEM_MIN_MEMORY_LIMIT = 0 # kilobytes
DMOJ_PROBLEM_MAX_MEMORY_LIMIT = 1048576 # kilobytes
DMOJ_PROBLEM_MIN_FILE_SIZE_LIMIT = 0 # kilobytes
DMOJ_PROBLEM_MAX_FILE_SIZE_LIMIT = 102400 # kilobytes (100 MB)
DMOJ_PROBLEM_MIN_PROBLEM_POINTS = 0
DMOJ_PROBLEM_HOT_PROBLEM_COUNT = 7

Expand Down
2 changes: 1 addition & 1 deletion judge/admin/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class Meta:

class LanguageLimitInline(admin.TabularInline):
model = LanguageLimit
fields = ('language', 'time_limit', 'memory_limit')
fields = ('language', 'time_limit', 'memory_limit', 'file_size_limit')
form = LanguageLimitInlineForm


Expand Down
9 changes: 7 additions & 2 deletions judge/bridge/judge_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,13 @@ def get_related_submission_data(self, submission):
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())
lang_limit = LanguageLimit.objects.filter(problem__id=pid, language__id=lid).values_list(
'time_limit', 'memory_limit', 'file_size_limit').get()
time = lang_limit[0]
memory = lang_limit[1]
# Use problem-specific file size limit if set, otherwise fall back to language default
if lang_limit[2] is not None:
file_size_limit = lang_limit[2]
except LanguageLimit.DoesNotExist:
pass

Expand Down
91 changes: 58 additions & 33 deletions judge/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def clean_time_limit(self):

class Meta:
model = LanguageLimit
fields = ('language', 'time_limit', 'memory_limit')
fields = ('language', 'time_limit', 'memory_limit', 'file_size_limit')
widgets = {
'language': Select2Widget(attrs={'style': 'width:200px'}),
}
Expand Down Expand Up @@ -377,40 +377,65 @@ def clean(self):
self.check_submission()
return cleaned_data

def _get_file_size_limit(self, language, problem):
"""Get effective file size limit in KB (problem-specific or language default)"""
limit = language.file_size_limit
if problem and limit >= 0:
try:
lang_limit = LanguageLimit.objects.get(problem=problem, language=language)
if lang_limit.file_size_limit is not None:
limit = lang_limit.file_size_limit
except LanguageLimit.DoesNotExist:
pass
return limit

def check_submission(self):
source = self.cleaned_data.get('source', '')
content = self.files.get('submission_file')
lang_obj = self.cleaned_data.get('language')

if lang_obj is not None:
if (source != '' and content is not None) or (source == '' and content is None) or \
(source != '' and lang_obj.file_only) or (content == '' and not lang_obj.file_only):
raise forms.ValidationError(_('Source code/file is missing or redundant. Please try again'))

if content:
max_file_size = lang_obj.file_size_limit * 1024 * 1024
ext = os.path.splitext(content.name)[1][1:]

if ext.lower() != lang_obj.extension.lower():
raise forms.ValidationError(_('Wrong file type for language %(lang)s, expected %(lang_ext)s'
', found %(ext)s')
% {'lang': lang_obj, 'lang_ext': lang_obj.extension, 'ext': ext})

elif content.size > max_file_size:
raise forms.ValidationError(_('File size is too big! Maximum file size is %s')
% filesizeformat(max_file_size))

if lang_obj.key == 'SCRATCH':
try:
archive = zipfile.ZipFile(content.file)
info = archive.getinfo('project.json')
if info.file_size > max_file_size:
raise forms.ValidationError(_('project.json is too big! Maximum file size is %s')
% filesizeformat(max_file_size))

self.files['submission_file'].file = archive.open('project.json')
except (zipfile.BadZipFile, KeyError):
pass
content = self.files.get('submission_file', None)
language = self.cleaned_data.get('language', None)
lang_obj = Language.objects.get(name=language)

if (source != '' and content is not None) or (source == '' and content is None) or \
(source != '' and lang_obj.file_only) or (content == '' and not lang_obj.file_only):
raise forms.ValidationError(_('Source code/file is missing or redundant. Please try again'))

problem = self.instance.problem if self.instance and self.instance.problem else None
max_size_kb = self._get_file_size_limit(lang_obj, problem)

# Validate source code size
if source and max_size_kb > 0:
source_size_bytes = len(source.encode('utf-8'))
if source_size_bytes > max_size_kb * 1024:
raise forms.ValidationError(
_('Source code is too large! Size: %(size)s, Maximum allowed: %(max)s') % {
'size': filesizeformat(source_size_bytes),
'max': filesizeformat(max_size_kb * 1024),
},
)

# Validate file upload
if content:
ext = os.path.splitext(content.name)[1][1:]
if ext.lower() != lang_obj.extension.lower():
raise forms.ValidationError(_('Wrong file type for language %(lang)s, expected %(lang_ext)s'
', found %(ext)s')
% {'lang': language, 'lang_ext': lang_obj.extension, 'ext': ext})

if max_size_kb > 0 and content.size > max_size_kb * 1024:
raise forms.ValidationError(_('File size is too big! Maximum file size is %s')
% filesizeformat(max_size_kb * 1024))

if lang_obj.key == 'SCRATCH':
try:
archive = zipfile.ZipFile(content.file)
info = archive.getinfo('project.json')
if info.file_size > max_size_kb * 1024:
raise forms.ValidationError(_('project.json is too big! Maximum file size is %s')
% filesizeformat(max_size_kb * 1024))

self.files['submission_file'].file = archive.open('project.json')
except (zipfile.BadZipFile, KeyError):
pass

def __init__(self, *args, judge_choices=(), **kwargs):
super(ProblemSubmitForm, self).__init__(*args, **kwargs)
Expand Down

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions judge/migrations/0221_alter_language_file_size_limit_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.2.25 on 2026-02-01 08:53

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('judge', '0220_languagelimit_file_size_limit_alter_contest_authors_and_more'),
]

operations = [
migrations.AlterField(
model_name='language',
name='file_size_limit',
field=models.IntegerField(blank=True, default=0, help_text='Limit of file size (in KB) if allow submit via file', verbose_name='Limit of file size'),
),
migrations.AlterField(
model_name='languagelimit',
name='file_size_limit',
field=models.IntegerField(blank=True, help_text='File size limit in kilobytes (KB) for this problem and language. Leave blank to use the default language limit.', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(102400)], verbose_name='file size limit'),
),
]
9 changes: 9 additions & 0 deletions judge/models/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,15 @@ class LanguageLimit(models.Model):
memory_limit = models.IntegerField(verbose_name=_('memory limit'),
validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_MEMORY_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_MEMORY_LIMIT)])
file_size_limit = models.IntegerField(
verbose_name=_('file size limit'),
null=True,
blank=True,
validators=[MinValueValidator(settings.DMOJ_PROBLEM_MIN_FILE_SIZE_LIMIT),
MaxValueValidator(settings.DMOJ_PROBLEM_MAX_FILE_SIZE_LIMIT)],
help_text=_('File size limit in kilobytes (KB) for this problem and language. '
'Leave blank to use the default language limit.'),
)

class Meta:
unique_together = ('problem', 'language')
Expand Down
2 changes: 1 addition & 1 deletion judge/models/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Language(models.Model):
help_text=_('If this language is submitted using file or not'))

file_size_limit = models.IntegerField(verbose_name=_('Limit of file size'), default=0, blank=True,
help_text=_('Limit of file size (in MB) if allow submit via file'))
help_text=_('Limit of file size (in KB) if allow submit via file'))

include_in_problem = models.BooleanField(verbose_name=_('Include in problems'), default=False,
help_text=_('If true, this language will be added to all problems'))
Expand Down
28 changes: 25 additions & 3 deletions judge/views/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from judge.comments import CommentedDetailView
from judge.forms import LanguageLimitFormSet, ProblemCloneForm, ProblemEditForm, ProblemEditTypeGroupForm, \
ProblemImportPolygonForm, ProblemImportPolygonStatementFormSet, ProblemSubmitForm, ProposeProblemSolutionFormSet
from judge.models import ContestSubmission, Judge, Language, Problem, ProblemGroup, \
from judge.models import ContestSubmission, Judge, Language, LanguageLimit, Problem, ProblemGroup, \
ProblemTranslation, ProblemType, RuntimeVersion, Solution, Submission, SubmissionSource
from judge.tasks import on_new_problem
from judge.template_context import misc_config
Expand Down Expand Up @@ -791,10 +791,32 @@ def get_title(self):
def get_form(self, form_class=None):
return self.get_submit_form()

def _get_file_size_limit(self, language, problem):
"""Get effective file size limit in KB (problem-specific or language default)"""
limit = language.file_size_limit
try:
lang_limit = LanguageLimit.objects.get(problem=problem, language=language)
if lang_limit.file_size_limit is not None:
limit = lang_limit.file_size_limit
except LanguageLimit.DoesNotExist:
pass
return limit

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(self.get_submit_context())
context['no_judges'] = not context['form'].fields['language'].queryset if context.get('form') else True
context['langs'] = Language.objects.all()
context['no_judges'] = not context['form'].fields['language'].queryset
context['submission_limit'] = self.contest_problem and self.contest_problem.max_submissions
context['submissions_left'] = self.remaining_submission_count
context['ACE_URL'] = settings.ACE_URL
context['default_lang'] = self.default_language

# Build map of language ID to effective file size limit
context['lang_file_size_limits'] = {
lang.id: self._get_file_size_limit(lang, self.object)
for lang in context['form'].fields['language'].queryset
}

return context

def form_valid(self, form):
Expand Down
4 changes: 3 additions & 1 deletion templates/problem/editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@
{% macro form_as_row(form) -%}
{% if form.non_field_errors() %}
<tr>
<td colspan="6">{{ form.non_field_errors() }}</td>
<td colspan="7">{{ form.non_field_errors() }}</td>
</tr>
{% endif %}
<tr>
<td>{{ form.id }} {{ form.language.errors }}{{ form.language }}</td>
<td>{{ form.time_limit.errors }}{{ form.time_limit }}</td>
<td>{{ form.memory_limit.errors }}{{ form.memory_limit }}</td>
<td>{{ form.file_size_limit.errors }}{{ form.file_size_limit }}</td>
<td>
{% if lang_limit_formset.can_delete and form.instance.pk %}
{{ form.DELETE }}
Expand Down Expand Up @@ -119,6 +120,7 @@ <h3 id="lang_limit_title">{{ _('Language-specific resource limit') }} <i class="
<th style="width: 20em;"> {{ _('Language') }} </th>
<th> {{ _('Time limit (seconds)') }} </th>
<th> {{ _('Memory limit (KiB)') }} </th>
<th> {{ _('File size limit (KB)') }} </th>
<th> {{ _('Delete') }} </th>
</tr>
</thead>
Expand Down
Loading
Loading