diff --git a/AKModel/forms.py b/AKModel/forms.py index 43f712ed9614a6727abd8488620c7d1db953307d..8554efd9b65c5fc03dbf74960db7eaac5ac3505a 100644 --- a/AKModel/forms.py +++ b/AKModel/forms.py @@ -73,3 +73,27 @@ class NewEventWizardActivateForm(forms.ModelForm): class AdminIntermediateForm(forms.Form): pass + + +class SlideExportForm(AdminIntermediateForm): + num_next = forms.IntegerField( + min_value=0, + max_value=6, + initial=3, + label=_("# next AKs"), + help_text=_("How many next AKs should be shown on a slide?")) + presentation_mode = forms.TypedChoiceField( + initial=False, + label=_("Presentation only?"), + widget=forms.RadioSelect, + choices=((True, _('Yes')), (False, _('No'))), + coerce=lambda x: x == "True", + help_text=_("Restrict AKs to those that asked for chance to be presented?")) + wish_notes = forms.TypedChoiceField( + initial=False, + label=_("Space for notes in wishes?"), + widget=forms.RadioSelect, + choices=((True, _('Yes')), (False, _('No'))), + coerce=lambda x: x == "True", + help_text=_("Create symbols indicating space to note down owners and timeslots for wishes, e.g., to be filled " + "out on a touch screen while presenting?")) diff --git a/AKModel/templates/admin/AKModel/status.html b/AKModel/templates/admin/AKModel/status.html index 362c72406804e4973d9600363511aac46226bbe4..19d753c9b904ede318d23fd689e97f59c5c62447 100644 --- a/AKModel/templates/admin/AKModel/status.html +++ b/AKModel/templates/admin/AKModel/status.html @@ -89,9 +89,7 @@ <a class="btn btn-success" href="{% url 'admin:ak_wiki_export' slug=event.slug %}">{% trans "Export AKs for Wiki" %}</a> <a class="btn btn-success" - href="{% url 'admin:ak_slide_export' event_slug=event.slug %}?num_next=3&wish_notes=False">{% trans "Export AK Slides" %}</a> - <a class="btn btn-success" - href="{% url 'admin:ak_slide_export' event_slug=event.slug %}?num_next=3&presentation_mode">{% trans "Export AK Slides (Presentation AKs only)" %}</a> + href="{% url 'admin:ak_slide_export' event_slug=event.slug %}">{% trans "Export AK Slides" %}</a> {% endif %} <h3 class="block-header">{% trans "Requirements" %}</h3> diff --git a/AKModel/urls.py b/AKModel/urls.py index ca9cfe6745aa9321db95561536d1bc42cad13fb6..e86661fd425a8d8b9c35b4524d4a027d77118dfb 100644 --- a/AKModel/urls.py +++ b/AKModel/urls.py @@ -6,7 +6,7 @@ from rest_framework.routers import DefaultRouter from AKModel import views from AKModel.views import NewEventWizardStartView, NewEventWizardSettingsView, NewEventWizardPrepareImportView, \ NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, EventStatusView, \ - AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView, export_slides + AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView, ExportSlidesView api_router = DefaultRouter() api_router.register('akowner', views.AKOwnerViewSet, basename='AKOwner') @@ -81,6 +81,5 @@ def get_admin_urls_event(admin_site): name="ak_wiki_export"), path('<slug:event_slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()), name="ak_delete_orga_messages"), - path('<slug:event_slug>/ak-slide-export/', export_slides, name="ak_slide_export"), - + path('<slug:event_slug>/ak-slide-export/', ExportSlidesView.as_view(), name="ak_slide_export"), ] diff --git a/AKModel/views.py b/AKModel/views.py index 08f054f14f9812dbec9cf61db67703c700d92db0..530a26c5f9d0a9ab9ba6245a135e6fc995c0e74a 100644 --- a/AKModel/views.py +++ b/AKModel/views.py @@ -1,18 +1,18 @@ -from abc import ABC, abstractmethod +import os +import tempfile from itertools import zip_longest from django.contrib import admin, messages -from django.contrib.admin.views.decorators import staff_member_required -from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views.generic import TemplateView, DetailView, ListView, DeleteView, CreateView, FormView, UpdateView -from django_tex.shortcuts import render_to_pdf +from django_tex.core import render_template_with_context, run_tex_in_directory +from django_tex.response import PDFResponse from rest_framework import viewsets, permissions, mixins from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \ - NewEventWizardImportForm, NewEventWizardActivateForm, AdminIntermediateForm + NewEventWizardImportForm, NewEventWizardActivateForm, AdminIntermediateForm, SlideExportForm from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \ AKOwnerSerializer @@ -195,13 +195,12 @@ class AKWikiExportView(AdminViewMixin, DetailView): return context -class IntermediateAdminView(AdminViewMixin, FormView, ABC): +class IntermediateAdminView(AdminViewMixin, FormView): template_name = "admin/AKModel/action_intermediate.html" form_class = AdminIntermediateForm - @abstractmethod def get_preview(self): - pass + return "" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -217,9 +216,6 @@ class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView): def get_orga_messages_for_event(self, event): return AKOrgaMessage.objects.filter(ak__event=event) - def get_preview(self): - return None - def get_success_url(self): return reverse_lazy('admin:event_status', kwargs={'slug': self.event.slug}) @@ -326,41 +322,50 @@ class NewEventWizardFinishView(WizardViewMixin, DetailView): wizard_step = 6 -@staff_member_required -def export_slides(request, event_slug): - template_name = 'admin/AKModel/export/slides.tex' - - event = get_object_or_404(Event, slug=event_slug) - - NEXT_AK_LIST_LENGTH = int(request.GET["num_next"]) if "num_next" in request.GET else 3 - RESULT_PRESENTATION_MODE = True if "presentation_mode" in request.GET else False - SPACE_FOR_NOTES_IN_WISHES = request.GET["wish_notes"] == "True" if "wish_notes" in request.GET else False - - translations = { - 'symbols': _("Symbols"), - 'who': _("Who?"), - 'duration': _("Duration(s)"), - 'reso': _("Reso intention?"), - 'category': _("Category (for Wishes)"), - 'wishes': _("Wishes"), - } - - def build_ak_list_with_next_aks(ak_list): - next_aks_list = zip_longest(*[ak_list[i + 1:] for i in range(NEXT_AK_LIST_LENGTH)], fillvalue=None) - return [(ak, next_aks) for ak, next_aks in zip_longest(ak_list, next_aks_list, fillvalue=list())] - - categories_with_aks, ak_wishes = event.get_categories_with_aks(wishes_seperately=True, filter=lambda - ak: not RESULT_PRESENTATION_MODE or (ak.present or (ak.present is None and ak.category.present_by_default))) - - context = { - 'title': event.name, - 'categories_with_aks': [(category, build_ak_list_with_next_aks(ak_list)) for category, ak_list in - categories_with_aks], - 'subtitle': _("AKs"), - "wishes": build_ak_list_with_next_aks(ak_wishes), - "translations": translations, - "result_presentation_mode": RESULT_PRESENTATION_MODE, - "space_for_notes_in_wishes": SPACE_FOR_NOTES_IN_WISHES, - } - - return render_to_pdf(request, template_name, context, filename='slides.pdf') +class ExportSlidesView(EventSlugMixin, IntermediateAdminView): + title = _('Export AK Slides') + form_class = SlideExportForm + + def form_valid(self, form): + template_name = 'admin/AKModel/export/slides.tex' + + NEXT_AK_LIST_LENGTH = form.cleaned_data['num_next'] + RESULT_PRESENTATION_MODE = form.cleaned_data["presentation_mode"] + SPACE_FOR_NOTES_IN_WISHES = form.cleaned_data["wish_notes"] + + translations = { + 'symbols': _("Symbols"), + 'who': _("Who?"), + 'duration': _("Duration(s)"), + 'reso': _("Reso intention?"), + 'category': _("Category (for Wishes)"), + 'wishes': _("Wishes"), + } + + def build_ak_list_with_next_aks(ak_list): + next_aks_list = zip_longest(*[ak_list[i + 1:] for i in range(NEXT_AK_LIST_LENGTH)], fillvalue=None) + return [(ak, next_aks) for ak, next_aks in zip_longest(ak_list, next_aks_list, fillvalue=list())] + + categories_with_aks, ak_wishes = self.event.get_categories_with_aks(wishes_seperately=True, filter=lambda + ak: not RESULT_PRESENTATION_MODE or (ak.present or (ak.present is None and ak.category.present_by_default))) + + context = { + 'title': self.event.name, + 'categories_with_aks': [(category, build_ak_list_with_next_aks(ak_list)) for category, ak_list in + categories_with_aks], + 'subtitle': _("AKs"), + "wishes": build_ak_list_with_next_aks(ak_wishes), + "translations": translations, + "result_presentation_mode": RESULT_PRESENTATION_MODE, + "space_for_notes_in_wishes": SPACE_FOR_NOTES_IN_WISHES, + } + + source = render_template_with_context(template_name, context) + + # Perform real compilation (run latex twice for correct page numbers) + with tempfile.TemporaryDirectory() as tempdir: + run_tex_in_directory(source, tempdir, template_name=self.template_name) + os.remove(f'{tempdir}/texput.tex') + pdf = run_tex_in_directory(source, tempdir, template_name=self.template_name) + + return PDFResponse(pdf, filename='slides.pdf') diff --git a/requirements.txt b/requirements.txt index 2271a46748a00e2e888afe8e3930a83e915e1e08..5b8693e83e70dfa3367abf91b49e1c509afe9fa0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ django-simple-history==3.1.1 django-registration-redux==2.11 django-debug-toolbar==3.7.0 django-bootstrap-datepicker-plus==3.0.5 -django-tex @ git+https://github.com/bhaettasch/django-tex.git@66cc6567acde4db2ac971b7707652067e664392c +django-tex==1.1.10 django-csp==3.7 mysqlclient==2.0.3 # for production deployment pytz==2022.4