diff --git a/aleksis/core/migrations/0014_alter_pdffile_file.py b/aleksis/core/migrations/0014_alter_pdffile_file.py index 2668488942f93db37703cccc79b9a43b6403904b..b2cc3beb5e9faf8ab474749c8eec0be031b493db 100644 --- a/aleksis/core/migrations/0014_alter_pdffile_file.py +++ b/aleksis/core/migrations/0014_alter_pdffile_file.py @@ -1,8 +1,10 @@ # Generated by Django 3.2 on 2021-04-17 18:47 -import aleksis.core.models from django.db import migrations, models +def _get_upload_path(instance, filename): # noqa + return f"pdfs/{instance.secret}.pdf" + class Migration(migrations.Migration): @@ -14,6 +16,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='pdffile', name='file', - field=models.FileField(blank=True, null=True, upload_to=aleksis.core.models.PDFFile._get_upload_path, verbose_name='Generated PDF file'), + field=models.FileField(blank=True, null=True, upload_to=_get_upload_path, verbose_name='Generated PDF file'), ), ] diff --git a/aleksis/core/migrations/0018_pdffile_html_file.py b/aleksis/core/migrations/0018_pdffile_html_file.py new file mode 100644 index 0000000000000000000000000000000000000000..d802526def2b33b73362890f0c362d174f53e76c --- /dev/null +++ b/aleksis/core/migrations/0018_pdffile_html_file.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.3 on 2021-05-20 14:25 + +from django.db import migrations, models +from django.core.files.base import ContentFile + +def _get_default(): + return ContentFile("") + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0017_dashboardwidget_broken'), + ] + + operations = [ + migrations.RemoveField( + model_name='pdffile', + name='html', + ), + migrations.AddField( + model_name='pdffile', + name='html_file', + field=models.FileField(default=_get_default, upload_to='pdfs/', verbose_name='Generated HTML file'), + preserve_default=False, + ), + migrations.AlterField( + model_name='pdffile', + name='file', + field=models.FileField(blank=True, null=True, upload_to='pdfs/', verbose_name='Generated PDF file'), + ), + ] diff --git a/aleksis/core/models.py b/aleksis/core/models.py index ac862dc64eb0607dec4d745651319ebb1150a278..bd69c5ce0e911eca5a85cba226c0e7323149f821 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -24,6 +24,7 @@ from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ import jsonstore +from cachalot.api import cachalot_disabled from cache_memoize import cache_memoize from django_celery_results.models import TaskResult from dynamic_preferences.models import PerInstancePreferenceModel @@ -1005,41 +1006,20 @@ class PDFFile(ExtensibleModel): def _get_default_expiration(): # noqa return timezone.now() + timedelta(minutes=get_site_preferences()["general__pdf_expiration"]) - def _get_upload_path(instance, filename): # noqa - return f"pdfs/{instance.secret}.pdf" - person = models.ForeignKey( to=Person, on_delete=models.CASCADE, verbose_name=_("Owner"), related_name="pdf_files" ) expires_at = models.DateTimeField( verbose_name=_("File expires at"), default=_get_default_expiration ) - html = models.TextField(verbose_name=_("Rendered HTML")) + html_file = models.FileField(upload_to="pdfs/", verbose_name=_("Generated HTML file")) file = models.FileField( - upload_to=_get_upload_path, blank=True, null=True, verbose_name=_("Generated PDF file") + upload_to="pdfs/", blank=True, null=True, verbose_name=_("Generated PDF file") ) def __str__(self): return f"{self.person} ({self.pk})" - @property - def secret(self) -> str: - """Get secret needed for accessing the HTML page.""" - return hmac.new( - bytes(settings.SECRET_KEY, "utf-8"), - msg=bytes(self.html + str(self.expires_at), "utf-8"), - digestmod="sha256", - ).hexdigest() - - @property - def html_url(self) -> str: - """Get URL for the HTML page.""" - return ( - urlparse(reverse("html_for_pdf_file", args=[self.pk])) - ._replace(query=f"secret={self.secret}") - .geturl() - ) - class Meta: verbose_name = _("PDF file") verbose_name_plural = _("PDF files") @@ -1057,7 +1037,8 @@ class TaskUserAssignment(ExtensibleModel): def create_for_task_id(cls, task_id: str, user: "User") -> "TaskUserAssignment": # Use get_or_create to ensure the TaskResult exists # django-celery-results will later add the missing information - result, __ = TaskResult.objects.get_or_create(task_id=task_id) + with cachalot_disabled(): + result, __ = TaskResult.objects.get_or_create(task_id=task_id) return cls.objects.create(task_result=result, user=user) class Meta: diff --git a/aleksis/core/tests/models/test_pdffile.py b/aleksis/core/tests/models/test_pdffile.py index 778ff8b5676a93279f9f9261e9c27ccbe6d85939..1f1e936a8c72a6dca3c4796a18a69b9370adbb0a 100644 --- a/aleksis/core/tests/models/test_pdffile.py +++ b/aleksis/core/tests/models/test_pdffile.py @@ -3,6 +3,7 @@ import re from datetime import datetime, timedelta from django.core.files import File +from django.core.files.base import ContentFile from django.core.files.storage import default_storage from django.template.loader import render_to_string from django.test import TransactionTestCase, override_settings @@ -25,14 +26,13 @@ class PDFFIleTest(TransactionTestCase): _test_pdf = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test.pdf") def _get_test_html(self): - return render_to_string("core/pages/test_pdf.html") + return ContentFile(render_to_string("core/pages/test_pdf.html"), name="source.html") def test_pdf_file(self): dummy_person = Person.objects.create(first_name="Jane", last_name="Doe") html = self._get_test_html() - assert "html" in html - file_object = PDFFile.objects.create(person=dummy_person, html=html) + file_object = PDFFile.objects.create(person=dummy_person, html_file=html) assert isinstance(file_object.expires_at, datetime) assert file_object.expires_at > timezone.now() assert not bool(file_object.file) @@ -40,12 +40,12 @@ class PDFFIleTest(TransactionTestCase): with open(self._test_pdf, "rb") as f: file_object.file.save("print.pdf", File(f)) file_object.save() - re_base = r"pdfs/[a-zA-Z0-9]+\.pdf" + re_base = r"pdfs/print_[a-zA-Z0-9]+\.pdf" assert re.match(re_base, file_object.file.name) def test_delete_signal(self): dummy_person = Person.objects.create(first_name="Jane", last_name="Doe") - file_object = PDFFile.objects.create(person=dummy_person, html=self._get_test_html()) + file_object = PDFFile.objects.create(person=dummy_person, html_file=self._get_test_html()) with open(self._test_pdf, "rb") as f: file_object.file.save("print.pdf", File(f)) file_object.save() @@ -59,10 +59,10 @@ class PDFFIleTest(TransactionTestCase): def test_delete_expired_files(self): # Create test instances dummy_person = Person.objects.create(first_name="Jane", last_name="Doe") - file_object = PDFFile.objects.create(person=dummy_person, html=self._get_test_html()) + file_object = PDFFile.objects.create(person=dummy_person, html_file=self._get_test_html()) file_object2 = PDFFile.objects.create( person=dummy_person, - html=self._get_test_html(), + html_file=self._get_test_html(), expires_at=timezone.now() + timedelta(minutes=10), ) with open(self._test_pdf, "rb") as f: diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index a357db0d6a41f09c79f799e978eb7fd5a0214d2e..9ce20f1173af72efab3c0f89ab308211d255073a 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -210,7 +210,6 @@ urlpatterns = [ name="edit_default_dashboard", ), path("pdfs/<int:pk>/", views.RedirectToPDFFile.as_view(), name="redirect_to_pdf_file"), - path("pdfs/<int:pk>/html/", views.HTMLForPDFFile.as_view(), name="html_for_pdf_file"), ] # Add URLs for optional features diff --git a/aleksis/core/util/pdf.py b/aleksis/core/util/pdf.py index 0ca5b5a3c7ae3a535914f26433f2c6353cf956dd..01733d7247bd89b4b8a72b8e113992488ea85394 100644 --- a/aleksis/core/util/pdf.py +++ b/aleksis/core/util/pdf.py @@ -4,6 +4,7 @@ from tempfile import TemporaryDirectory from typing import Optional from django.core.files import File +from django.core.files.base import ContentFile from django.http.request import HttpRequest from django.http.response import HttpResponse from django.shortcuts import get_object_or_404 @@ -73,8 +74,10 @@ def render_pdf(request: HttpRequest, template_name: str, context: dict = None) - html_template = render_to_string(template_name, context, request) - file_object = PDFFile.objects.create(person=request.user.person, html=html_template) - html_url = request.build_absolute_uri(file_object.html_url) + file_object = PDFFile.objects.create( + person=request.user.person, html_file=ContentFile(html_template, name="source.html") + ) + html_url = request.build_absolute_uri(file_object.html_file.url) result = generate_pdf.delay(file_object.pk, html_url, lang=get_language()) diff --git a/aleksis/core/views.py b/aleksis/core/views.py index d7101305143eb21d84a3748ed5b10240a5629b38..76abd15f79a5b7db065227e8451fa7ef6629a8d7 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -1080,18 +1080,6 @@ class RedirectToPDFFile(SingleObjectMixin, View): return redirect(file_object.file.url) -class HTMLForPDFFile(SingleObjectMixin, View): - """Return rendered HTML for generating a PDF file.""" - - model = PDFFile - - def get(self, request, *args, **kwargs): - file_object = self.get_object() - if request.GET.get("secret") != file_object.secret: - raise PermissionDenied() - return HttpResponse(file_object.html) - - class CeleryProgressView(View): """Wrap celery-progress view to check permissions before."""