Skip to content
Snippets Groups Projects
Commit ec088aa2 authored by Benjamin Hättasch's avatar Benjamin Hättasch Committed by Nadja Geisler
Browse files

Introduce GUI for slide export

This allows specifying the parameters without the need to know the GET keys
Also resolve double-compiling issue and thus switch from custom version of django-tex to the latest official release
Minor improvements to generic admin action view
This implements #152
parent bfc5198b
No related branches found
No related tags found
No related merge requests found
...@@ -73,3 +73,27 @@ class NewEventWizardActivateForm(forms.ModelForm): ...@@ -73,3 +73,27 @@ class NewEventWizardActivateForm(forms.ModelForm):
class AdminIntermediateForm(forms.Form): class AdminIntermediateForm(forms.Form):
pass 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?"))
...@@ -89,9 +89,7 @@ ...@@ -89,9 +89,7 @@
<a class="btn btn-success" <a class="btn btn-success"
href="{% url 'admin:ak_wiki_export' slug=event.slug %}">{% trans "Export AKs for Wiki" %}</a> href="{% url 'admin:ak_wiki_export' slug=event.slug %}">{% trans "Export AKs for Wiki" %}</a>
<a class="btn btn-success" <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> href="{% url 'admin:ak_slide_export' event_slug=event.slug %}">{% 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>
{% endif %} {% endif %}
<h3 class="block-header">{% trans "Requirements" %}</h3> <h3 class="block-header">{% trans "Requirements" %}</h3>
......
...@@ -6,7 +6,7 @@ from rest_framework.routers import DefaultRouter ...@@ -6,7 +6,7 @@ from rest_framework.routers import DefaultRouter
from AKModel import views from AKModel import views
from AKModel.views import NewEventWizardStartView, NewEventWizardSettingsView, NewEventWizardPrepareImportView, \ from AKModel.views import NewEventWizardStartView, NewEventWizardSettingsView, NewEventWizardPrepareImportView, \
NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, EventStatusView, \ NewEventWizardImportView, NewEventWizardActivateView, NewEventWizardFinishView, EventStatusView, \
AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView, export_slides AKRequirementOverview, AKCSVExportView, AKWikiExportView, AKMessageDeleteView, ExportSlidesView
api_router = DefaultRouter() api_router = DefaultRouter()
api_router.register('akowner', views.AKOwnerViewSet, basename='AKOwner') api_router.register('akowner', views.AKOwnerViewSet, basename='AKOwner')
...@@ -81,6 +81,5 @@ def get_admin_urls_event(admin_site): ...@@ -81,6 +81,5 @@ def get_admin_urls_event(admin_site):
name="ak_wiki_export"), name="ak_wiki_export"),
path('<slug:event_slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()), path('<slug:event_slug>/delete-orga-messages/', admin_site.admin_view(AKMessageDeleteView.as_view()),
name="ak_delete_orga_messages"), 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"),
] ]
from abc import ABC, abstractmethod import os
import tempfile
from itertools import zip_longest from itertools import zip_longest
from django.contrib import admin, messages 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.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView, DetailView, ListView, DeleteView, CreateView, FormView, UpdateView 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 rest_framework import viewsets, permissions, mixins
from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \ 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.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement
from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \ from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \
AKOwnerSerializer AKOwnerSerializer
...@@ -195,13 +195,12 @@ class AKWikiExportView(AdminViewMixin, DetailView): ...@@ -195,13 +195,12 @@ class AKWikiExportView(AdminViewMixin, DetailView):
return context return context
class IntermediateAdminView(AdminViewMixin, FormView, ABC): class IntermediateAdminView(AdminViewMixin, FormView):
template_name = "admin/AKModel/action_intermediate.html" template_name = "admin/AKModel/action_intermediate.html"
form_class = AdminIntermediateForm form_class = AdminIntermediateForm
@abstractmethod
def get_preview(self): def get_preview(self):
pass return ""
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
...@@ -217,9 +216,6 @@ class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView): ...@@ -217,9 +216,6 @@ class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView):
def get_orga_messages_for_event(self, event): def get_orga_messages_for_event(self, event):
return AKOrgaMessage.objects.filter(ak__event=event) return AKOrgaMessage.objects.filter(ak__event=event)
def get_preview(self):
return None
def get_success_url(self): def get_success_url(self):
return reverse_lazy('admin:event_status', kwargs={'slug': self.event.slug}) return reverse_lazy('admin:event_status', kwargs={'slug': self.event.slug})
...@@ -326,41 +322,50 @@ class NewEventWizardFinishView(WizardViewMixin, DetailView): ...@@ -326,41 +322,50 @@ class NewEventWizardFinishView(WizardViewMixin, DetailView):
wizard_step = 6 wizard_step = 6
@staff_member_required class ExportSlidesView(EventSlugMixin, IntermediateAdminView):
def export_slides(request, event_slug): title = _('Export AK Slides')
template_name = 'admin/AKModel/export/slides.tex' form_class = SlideExportForm
event = get_object_or_404(Event, slug=event_slug) def form_valid(self, form):
template_name = 'admin/AKModel/export/slides.tex'
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 NEXT_AK_LIST_LENGTH = form.cleaned_data['num_next']
SPACE_FOR_NOTES_IN_WISHES = request.GET["wish_notes"] == "True" if "wish_notes" in request.GET else False RESULT_PRESENTATION_MODE = form.cleaned_data["presentation_mode"]
SPACE_FOR_NOTES_IN_WISHES = form.cleaned_data["wish_notes"]
translations = {
'symbols': _("Symbols"), translations = {
'who': _("Who?"), 'symbols': _("Symbols"),
'duration': _("Duration(s)"), 'who': _("Who?"),
'reso': _("Reso intention?"), 'duration': _("Duration(s)"),
'category': _("Category (for Wishes)"), 'reso': _("Reso intention?"),
'wishes': _("Wishes"), '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) def build_ak_list_with_next_aks(ak_list):
return [(ak, next_aks) for ak, next_aks in zip_longest(ak_list, next_aks_list, fillvalue=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))) 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': event.name, context = {
'categories_with_aks': [(category, build_ak_list_with_next_aks(ak_list)) for category, ak_list in 'title': self.event.name,
categories_with_aks], 'categories_with_aks': [(category, build_ak_list_with_next_aks(ak_list)) for category, ak_list in
'subtitle': _("AKs"), categories_with_aks],
"wishes": build_ak_list_with_next_aks(ak_wishes), 'subtitle': _("AKs"),
"translations": translations, "wishes": build_ak_list_with_next_aks(ak_wishes),
"result_presentation_mode": RESULT_PRESENTATION_MODE, "translations": translations,
"space_for_notes_in_wishes": SPACE_FOR_NOTES_IN_WISHES, "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')
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')
...@@ -8,7 +8,7 @@ django-simple-history==3.1.1 ...@@ -8,7 +8,7 @@ django-simple-history==3.1.1
django-registration-redux==2.11 django-registration-redux==2.11
django-debug-toolbar==3.7.0 django-debug-toolbar==3.7.0
django-bootstrap-datepicker-plus==3.0.5 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 django-csp==3.7
mysqlclient==2.0.3 # for production deployment mysqlclient==2.0.3 # for production deployment
pytz==2022.4 pytz==2022.4
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment