diff --git a/django_ace/static/django_ace/widget.js b/django_ace/static/django_ace/widget.js index d097b06..8980ed7 100644 --- a/django_ace/static/django_ace/widget.js +++ b/django_ace/static/django_ace/widget.js @@ -77,15 +77,17 @@ mode = widget.getAttribute('data-mode'), theme = widget.getAttribute('data-theme'), wordwrap = widget.getAttribute('data-wordwrap'), - toolbar = prev(widget), - main_block = toolbar.parentNode; + toolbar = prev(widget); + var main_block = div.parentNode.parentNode; - // Toolbar maximize/minimize button - var min_max = toolbar.getElementsByClassName('django-ace-max_min'); - min_max[0].onclick = function () { - minimizeMaximize(widget, main_block, editor); - return false; - }; + if (toolbar != null) { + // Toolbar maximize/minimize button + var min_max = toolbar.getElementsByClassName('django-ace-max_min'); + min_max[0].onclick = function () { + minimizeMaximize(widget, main_block, editor); + return false; + }; + } editor.getSession().setValue(textarea.value); diff --git a/django_ace/widgets.py b/django_ace/widgets.py index 38fcd75..ec4df40 100644 --- a/django_ace/widgets.py +++ b/django_ace/widgets.py @@ -19,6 +19,7 @@ class AceWidget(forms.Textarea): width="100%", height="300px", no_ace_media=False, + toolbar=True, *args, **kwargs ): @@ -28,6 +29,7 @@ class AceWidget(forms.Textarea): self.width = width self.height = height self.ace_media = not no_ace_media + self.toolbar = toolbar super(AceWidget, self).__init__(*args, **kwargs) @property @@ -61,10 +63,14 @@ class AceWidget(forms.Textarea): html = "
%s" % (flatatt(ace_attrs), textarea) - # add toolbar - html = ( - '
' - '
%s
' - ) % html + if self.toolbar: + toolbar = ( + '
' + '' + "
" + ).format(self.width) + html = toolbar + html + + html = '
{}
'.format(html) return mark_safe(html) diff --git a/judge/admin/problem.py b/judge/admin/problem.py index 88d260b..f178023 100644 --- a/judge/admin/problem.py +++ b/judge/admin/problem.py @@ -9,12 +9,15 @@ from django.forms import ModelForm from django.urls import reverse_lazy from django.utils.html import format_html from django.utils.translation import gettext, gettext_lazy as _, ungettext +from django_ace import AceWidget + from reversion.admin import VersionAdmin from reversion_compare.admin import CompareVersionAdmin from judge.models import ( LanguageLimit, + LanguageTemplate, Problem, ProblemClarification, ProblemTranslation, @@ -100,6 +103,20 @@ class LanguageLimitInline(admin.TabularInline): form = LanguageLimitInlineForm +class LanguageTemplateInlineForm(ModelForm): + class Meta: + widgets = { + "language": AdminSelect2Widget, + "source": AceWidget(width="600px", height="200px", toolbar=False), + } + + +class LanguageTemplateInline(admin.TabularInline): + model = LanguageTemplate + fields = ("language", "source") + form = LanguageTemplateInlineForm + + class ProblemClarificationForm(ModelForm): class Meta: if HeavyPreviewPageDownWidget is not None: @@ -208,6 +225,7 @@ class ProblemAdmin(CompareVersionAdmin): ) inlines = [ LanguageLimitInline, + LanguageTemplateInline, ProblemClarificationInline, ProblemSolutionInline, ProblemTranslationInline, diff --git a/judge/migrations/0126_languagetemplate.py b/judge/migrations/0126_languagetemplate.py new file mode 100644 index 0000000..7555905 --- /dev/null +++ b/judge/migrations/0126_languagetemplate.py @@ -0,0 +1,54 @@ +# Generated by Django 2.2.25 on 2022-06-12 06:59 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0125_auto_20220602_1216"), + ] + + operations = [ + migrations.CreateModel( + name="LanguageTemplate", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "source", + models.TextField(max_length=65536, verbose_name="source code"), + ), + ( + "language", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="judge.Language", + verbose_name="language", + ), + ), + ( + "problem", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="language_templates", + to="judge.Problem", + verbose_name="problem", + ), + ), + ], + options={ + "verbose_name": "language-specific template", + "verbose_name_plural": "language-specific templates", + "unique_together": {("problem", "language")}, + }, + ), + ] diff --git a/judge/models/__init__.py b/judge/models/__init__.py index a8232da..dcb7a1e 100644 --- a/judge/models/__init__.py +++ b/judge/models/__init__.py @@ -20,6 +20,7 @@ from judge.models.interface import BlogPost, MiscConfig, NavigationBar, validate from judge.models.message import PrivateMessage, PrivateMessageThread from judge.models.problem import ( LanguageLimit, + LanguageTemplate, License, Problem, ProblemClarification, diff --git a/judge/models/problem.py b/judge/models/problem.py index 4562775..6ef8bdc 100644 --- a/judge/models/problem.py +++ b/judge/models/problem.py @@ -639,6 +639,24 @@ class LanguageLimit(models.Model): verbose_name_plural = _("language-specific resource limits") +class LanguageTemplate(models.Model): + problem = models.ForeignKey( + Problem, + verbose_name=_("problem"), + related_name="language_templates", + on_delete=CASCADE, + ) + language = models.ForeignKey( + Language, verbose_name=_("language"), on_delete=CASCADE + ) + source = models.TextField(verbose_name=_("source code"), max_length=65536) + + class Meta: + unique_together = ("problem", "language") + verbose_name = _("language-specific template") + verbose_name_plural = _("language-specific templates") + + class Solution(models.Model): problem = models.OneToOneField( Problem, diff --git a/judge/views/problem.py b/judge/views/problem.py index aa913d8..a05936f 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -53,6 +53,7 @@ from judge.models import ( Organization, VolunteerProblemVote, Profile, + LanguageTemplate, ) from judge.pdf_problems import DefaultPdfMaker, HAS_PDF from judge.utils.diggpaginator import DiggPaginator @@ -895,10 +896,21 @@ class ProblemFeed(ProblemList): class LanguageTemplateAjax(View): def get(self, request, *args, **kwargs): try: - language = get_object_or_404(Language, id=int(request.GET.get("id", 0))) + problem = request.GET.get("problem", None) + lang_id = int(request.GET.get("id", 0)) + res = None + if problem: + try: + res = LanguageTemplate.objects.get( + language__id=lang_id, problem__id=problem + ).source + except ObjectDoesNotExist: + pass + if not res: + res = get_object_or_404(Language, id=lang_id).template except ValueError: raise Http404() - return HttpResponse(language.template, content_type="text/plain") + return HttpResponse(res, content_type="text/plain") class RandomProblem(ProblemList): @@ -1107,6 +1119,7 @@ def problem_submit(request, problem, submission=None): "submissions_left": submissions_left, "ACE_URL": settings.ACE_URL, "default_lang": default_lang, + "problem_id": problem.id, }, ) diff --git a/templates/problem/submit.html b/templates/problem/submit.html index f940e23..a10c667 100644 --- a/templates/problem/submit.html +++ b/templates/problem/submit.html @@ -30,7 +30,8 @@ update_submit_area(code); } else { $.get('{{ url('language_template_ajax') }}', { - id: lang_id + id: lang_id, + problem: {{problem_id}} }).done(function (template) { update_submit_area(template); });