diff --git a/dmoj/urls.py b/dmoj/urls.py index 8987966..39c66f0 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -256,6 +256,7 @@ urlpatterns = [ problem.ProblemPdfView.as_view(), name="problem_pdf", ), + url(r"^/pdf_description$", problem.ProblemPdfDescriptionView.as_view(), name="problem_pdf_description"), url(r"^/clone", problem.ProblemClone.as_view(), name="problem_clone"), url(r"^/submit$", problem.problem_submit, name="problem_submit"), url( diff --git a/judge/admin/problem.py b/judge/admin/problem.py index 53db1d8..15337d2 100644 --- a/judge/admin/problem.py +++ b/judge/admin/problem.py @@ -193,6 +193,7 @@ class ProblemAdmin(CompareVersionAdmin): "is_organization_private", "organizations", "description", + "pdf_description", "license", ), }, diff --git a/judge/migrations/0130_auto_20220831_1048.py b/judge/migrations/0130_auto_20220831_1048.py new file mode 100644 index 0000000..f4983b6 --- /dev/null +++ b/judge/migrations/0130_auto_20220831_1048.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.25 on 2022-08-31 03:48 + +from django.db import migrations, models +import judge.models.problem +import judge.utils.problem_data + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0129_auto_20220622_1424'), + ] + + operations = [ + migrations.AddField( + model_name='problem', + name='pdf_description', + field=models.FileField(blank=True, null=True, storage=judge.utils.problem_data.ProblemDataStorage(), upload_to=judge.models.problem.problem_directory_file, verbose_name='pdf statement'), + ), + migrations.AlterField( + model_name='problem', + name='description', + field=models.TextField(blank=True, verbose_name='problem body'), + ), + ] diff --git a/judge/models/problem.py b/judge/models/problem.py index ad96793..0af6b76 100644 --- a/judge/models/problem.py +++ b/judge/models/problem.py @@ -18,6 +18,7 @@ from judge.models.profile import Organization, Profile from judge.models.runtime import Language from judge.user_translations import gettext as user_gettext from judge.utils.raw_sql import RawSQLColumn, unique_together_left_join +from judge.models.problem_data import problem_data_storage, problem_directory_file_helper __all__ = [ "ProblemGroup", @@ -31,6 +32,8 @@ __all__ = [ "TranslatedProblemForeignKeyQuerySet", ] +def problem_directory_file(data, filename): + return problem_directory_file_helper(data.code, filename) class ProblemType(models.Model): name = models.CharField( @@ -160,7 +163,7 @@ class Problem(models.Model): db_index=True, help_text=_("The full name of the problem, " "as shown in the problem list."), ) - description = models.TextField(verbose_name=_("problem body")) + description = models.TextField(verbose_name=_("problem body"), blank=True) authors = models.ManyToManyField( Profile, verbose_name=_("creators"), @@ -299,6 +302,13 @@ class Problem(models.Model): is_organization_private = models.BooleanField( verbose_name=_("private to organizations"), default=False ) + pdf_description = models.FileField( + verbose_name=_("pdf statement"), + storage=problem_data_storage, + null=True, + blank=True, + upload_to=problem_directory_file, + ) def __init__(self, *args, **kwargs): super(Problem, self).__init__(*args, **kwargs) diff --git a/judge/models/problem_data.py b/judge/models/problem_data.py index c47559a..d1b9204 100644 --- a/judge/models/problem_data.py +++ b/judge/models/problem_data.py @@ -20,12 +20,12 @@ __all__ = [ problem_data_storage = ProblemDataStorage() -def _problem_directory_file(code, filename): +def problem_directory_file_helper(code, filename): return os.path.join(code, os.path.basename(filename)) def problem_directory_file(data, filename): - return _problem_directory_file(data.problem.code, filename) + return problem_directory_file_helper(data.problem.code, filename) CHECKERS = ( diff --git a/judge/views/problem.py b/judge/views/problem.py index 100220a..d1d6730 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -402,6 +402,27 @@ class ProblemPdfView(ProblemMixin, SingleObjectMixin, View): return response +class ProblemPdfDescriptionView(ProblemMixin, SingleObjectMixin, View): + def get(self, request, *args, **kwargs): + problem = self.get_object() + if not problem.pdf_description: + raise Http404() + response = HttpResponse() + if hasattr(settings, "DMOJ_PDF_PROBLEM_INTERNAL") and request.META.get( + "SERVER_SOFTWARE", "" + ).startswith("nginx/"): + response["X-Accel-Redirect"] = problem.pdf_description.path + else: + with open(problem.pdf_description.path, "rb") as f: + response.content = f.read() + + response["Content-Type"] = "application/pdf" + response["Content-Disposition"] = "inline; filename=%s.pdf" % ( + problem.code, + ) + return response + + class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView): model = Problem title = gettext_lazy("Problems") diff --git a/templates/problem/problem.html b/templates/problem/problem.html index 04c4b0c..8f6c98f 100644 --- a/templates/problem/problem.html +++ b/templates/problem/problem.html @@ -377,6 +377,10 @@ {{ description|markdown("problem", MATH_ENGINE)|reference|str|safe }} {% endcache %} + {% if problem.pdf_description %} + + {% endif %} + {% with license=problem.license %} {% if license %}