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: 1 addition & 1 deletion judge/admin/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class ContestProblemInline(SortableInlineAdminMixin, admin.TabularInline):
verbose_name = _('Problem')
verbose_name_plural = _('Problems')
fields = ('problem', 'points', 'partial', 'is_pretested', 'max_submissions', 'output_prefix_override', 'order',
'rejudge_column', 'rescore_column')
'rejudge_column', 'rescore_column', 'name_override', 'pdf_url_override', 'description_override')
readonly_fields = ('rejudge_column', 'rescore_column')
form = ContestProblemInlineForm

Expand Down
18 changes: 18 additions & 0 deletions judge/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,16 +682,34 @@ def __init__(self, *args, **kwargs):

self.fields['problem'].queryset = Problem.get_visible_problems(self.user)

statement_file = forms.FileField(
required=False,
label=_('Statement file (PDF)'),
help_text=_('Upload a PDF to override the problem statement for this contest.'),
)

def save(self, commit=True):
instance = super(ProposeContestProblemForm, self).save(commit=False)
statement_file = self.cleaned_data.get('statement_file')
if statement_file:
from judge.views.widgets import pdf_statement_uploader
instance.pdf_url_override = pdf_statement_uploader(statement_file)
if commit:
instance.save()
return instance

class Meta:
model = ContestProblem
verbose_name = _('Problem')
verbose_name_plural = 'Problems'
fields = (
'problem', 'points', 'order', 'max_submissions',
'name_override', 'pdf_url_override', 'description_override',
)

widgets = {
'problem': HeavySelect2Widget(data_view='problem_select2', attrs={'style': 'width: 100%'}),
'description_override': MartorWidget(attrs={'data-markdownfy-url': reverse_lazy('problem_preview')}),
}

error_messages = {
Expand Down
58 changes: 58 additions & 0 deletions judge/migrations/0216_contest_problem_override_fields.py

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions judge/models/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,35 @@ class ContestProblem(models.Model):
validators=[MinValueOrNoneValidator(1, _('Why include a problem you '
"can't submit to?"))])

# Override fields for contest-specific statements
name_override = models.CharField(max_length=256, verbose_name=_('name override'), blank=True,
help_text=_('Override the problem name for this contest. '
'Leave blank to use original.'))
description_override = models.TextField(verbose_name=_('description override'), blank=True,
help_text=_('Override the problem description for this contest. '
'Leave blank to use original.'))
pdf_url_override = models.CharField(max_length=200, verbose_name=_('PDF statement override'), blank=True,
help_text=_('Override the PDF statement URL for this contest. '
'Leave blank to use original.'))

@property
def effective_name(self):
"""Return the name to display (override or original)."""
return self.name_override or self.problem.name

@property
def effective_pdf_url(self):
"""Return the PDF URL to use (override or original)."""
from judge.utils.url import get_absolute_pdf_url
if self.pdf_url_override:
return get_absolute_pdf_url(self.pdf_url_override)
return self.problem.absolute_pdf_url

@property
def effective_description(self):
"""Return the description to use (override or original)."""
return self.description_override or self.problem.description

class Meta:
unique_together = ('problem', 'contest')
verbose_name = _('contest problem')
Expand Down
12 changes: 12 additions & 0 deletions judge/views/contests.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,14 @@ def get_context_data(self, **kwargs):

# convert to problem points in contest instead of actual points
points_list = list(self.object.contest_problems.values_list('points').order_by('order'))
contest_problems_map = {cp.problem_id: cp for cp in self.object.contest_problems.all()}
for idx, p in enumerate(context['contest_problems']):
p.points = points_list[idx][0]
cp = contest_problems_map.get(p.id)
if cp:
p.effective_name = cp.effective_name
p.effective_pdf_url = cp.effective_pdf_url
p.effective_description = cp.effective_description

context['metadata'] = {
'has_public_editorials': any(
Expand Down Expand Up @@ -358,8 +364,14 @@ def get_context_data(self, **kwargs):

# convert to problem points in contest instead of actual points
points_list = list(self.object.contest_problems.values_list('points').order_by('order'))
contest_problems_map = {cp.problem_id: cp for cp in self.object.contest_problems.all()}
for idx, p in enumerate(context['contest_problems']):
p.points = points_list[idx][0]
cp = contest_problems_map.get(p.id)
if cp:
p.effective_name = cp.effective_name
p.effective_pdf_url = cp.effective_pdf_url
p.effective_description = cp.effective_description

authenticated = self.request.user.is_authenticated
context['completed_problem_ids'] = user_completed_ids(self.request.profile) if authenticated else []
Expand Down
11 changes: 11 additions & 0 deletions judge/views/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,17 @@ def get_context_data(self, **kwargs):
context['description'] = translation.description
context['translated'] = True

# Apply contest problem overrides if in contest
if contest_problem:
if contest_problem.name_override:
context['title'] = contest_problem.name_override
if contest_problem.description_override:
context['description'] = contest_problem.description_override
# Use effective_pdf_url which handles override or fallback
context['effective_pdf_url'] = contest_problem.effective_pdf_url
else:
context['effective_pdf_url'] = self.object.absolute_pdf_url

if not self.object.og_image or not self.object.summary:
metadata = generate_opengraph('generated-meta-problem:%s:%d' % (context['language'], self.object.id),
context['description'], 'problem')
Expand Down
4 changes: 3 additions & 1 deletion templates/contest/contest-all-problems.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ <h2 style="margin-top: 0.5em; margin-right: 0.5em;">
<i class="attempted-problem-color fa fa-frown-o"></i>
</a>
{% endif %}
<a href="{{ url('problem_detail', problem.code) }}">{{ problem.i18n_name or problem.name }}</a>
<a href="{{ url('problem_detail', problem.code) }}">{{ problem.effective_name or problem.i18n_name or problem.name }}</a>
</h2>
<a href="{{ url('problem_submit', problem.code) }}" class="button" style="display: inline; margin: auto 0;">
{{ _('Submit') }}
Expand All @@ -45,7 +45,9 @@ <h2 style="margin-top: 0.5em; margin-right: 0.5em;">
<p><strong>{{ _('Points:') }}</strong> {{ problem.points }}</p>
</div>
</div>
{% with effective_pdf_url=problem.effective_pdf_url, description=problem.effective_description %}
{% include "problem/problem-detail.html" %}
{% endwith %}
<hr>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions templates/contest/contest.html
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,9 @@ <h2 style="margin-bottom: 0.2em; float:left;"><i class="fa fa-fw fa-question-cir
{% endif %}
<td style="text-align: left; padding-left: 2em;">
{% if can_view_all_problems or problem.is_public %}
<a href="{{ url('problem_detail', problem.code) }}">{{ problem.i18n_name or problem.name }}</a>
<a href="{{ url('problem_detail', problem.code) }}">{{ problem.effective_name or problem.i18n_name or problem.name }}</a>
{% else %}
{{ problem.i18n_name or problem.name }}
{{ problem.effective_name or problem.i18n_name or problem.name }}
{% endif %}
</td>
{% if not is_ICPC_format %}
Expand Down
2 changes: 1 addition & 1 deletion templates/problem/problem-detail.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% with pdf_url=problem.absolute_pdf_url %}
{% with pdf_url=effective_pdf_url or problem.absolute_pdf_url %}
{% if pdf_url %}
<p>{{ _("In case the statement didn't load correctly, you can download the statement here: ") }}<a href="{{ pdf_url }}">{{ _("Statement") }}</a></p>
<object id="pdfContainer" data="{{ pdf_url }}" type="application/pdf" height="1000" style="width: 100%;" ></object>
Expand Down
Loading