Skip to content
Snippets Groups Projects
Commit 84584d74 authored by Nadja Geisler's avatar Nadja Geisler :sunny:
Browse files

Merge branch 'feature-better-admin' into 'main'

Add new functionality to backend

Closes #153, #154, and #152

See merge request !133
parents bfc5198b f444f3a6
No related branches found
No related tags found
No related merge requests found
from django import forms from django import forms
from django.apps import apps from django.apps import apps
from django.contrib import admin, messages from django.contrib import admin, messages
from django.contrib.admin import SimpleListFilter, RelatedFieldListFilter, action from django.contrib.admin import SimpleListFilter, RelatedFieldListFilter, action, display
from django.db.models import Count, F from django.db.models import Count, F
from django.db.models.functions import Now from django.http import HttpResponseRedirect
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy, path
from django.utils import timezone from django.utils import timezone
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
...@@ -18,6 +18,8 @@ from AKModel.availability.models import Availability ...@@ -18,6 +18,8 @@ from AKModel.availability.models import Availability
from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \ from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \
ConstraintViolation ConstraintViolation
from AKModel.urls import get_admin_urls_event_wizard, get_admin_urls_event from AKModel.urls import get_admin_urls_event_wizard, get_admin_urls_event
from AKModel.views import CVMarkResolvedView, CVSetLevelViolationView, CVSetLevelWarningView, AKResetInterestView, \
AKResetInterestCounterView, PlanPublishView, PlanUnpublishView
class EventRelatedFieldListFilter(RelatedFieldListFilter): class EventRelatedFieldListFilter(RelatedFieldListFilter):
...@@ -36,7 +38,7 @@ class EventAdmin(admin.ModelAdmin): ...@@ -36,7 +38,7 @@ class EventAdmin(admin.ModelAdmin):
list_filter = ['active'] list_filter = ['active']
list_editable = ['active'] list_editable = ['active']
ordering = ['-start'] ordering = ['-start']
readonly_fields = ['status_url', 'plan_hidden', 'plan_published_at'] readonly_fields = ['status_url', 'plan_hidden', 'plan_published_at', 'toggle_plan_visibility']
actions = ['publish', 'unpublish'] actions = ['publish', 'unpublish']
def add_view(self, request, form_url='', extra_context=None): def add_view(self, request, form_url='', extra_context=None):
...@@ -50,14 +52,27 @@ class EventAdmin(admin.ModelAdmin): ...@@ -50,14 +52,27 @@ class EventAdmin(admin.ModelAdmin):
if apps.is_installed("AKScheduling"): if apps.is_installed("AKScheduling"):
from AKScheduling.urls import get_admin_urls_scheduling from AKScheduling.urls import get_admin_urls_scheduling
urls.extend(get_admin_urls_scheduling(self.admin_site)) urls.extend(get_admin_urls_scheduling(self.admin_site))
urls.extend([
path('plan/publish/', PlanPublishView.as_view(), name="plan-publish"),
path('plan/unpublish/', PlanUnpublishView.as_view(), name="plan-unpublish"),
])
urls.extend(super().get_urls()) urls.extend(super().get_urls())
return urls return urls
@display(description=_("Status"))
def status_url(self, obj): def status_url(self, obj):
return format_html("<a href='{url}'>{text}</a>", return format_html("<a href='{url}'>{text}</a>",
url=reverse_lazy('admin:event_status', kwargs={'slug': obj.slug}), text=_("Status")) url=reverse_lazy('admin:event_status', kwargs={'slug': obj.slug}), text=_("Status"))
status_url.short_description = _("Status") @display(description=_("Toggle plan visibility"))
def toggle_plan_visibility(self, obj):
if obj.plan_hidden:
url = f"{reverse_lazy('admin:plan-publish')}?pks={obj.pk}"
text = _('Publish plan')
else:
url = f"{reverse_lazy('admin:plan-unpublish')}?pks={obj.pk}"
text = _('Unpublish plan')
return format_html("<a href='{url}'>{text}</a>", url=url, text=text)
def get_form(self, request, obj=None, change=False, **kwargs): def get_form(self, request, obj=None, change=False, **kwargs):
# Use timezone of event # Use timezone of event
...@@ -66,13 +81,13 @@ class EventAdmin(admin.ModelAdmin): ...@@ -66,13 +81,13 @@ class EventAdmin(admin.ModelAdmin):
@action(description=_('Publish plan')) @action(description=_('Publish plan'))
def publish(self, request, queryset): def publish(self, request, queryset):
queryset.update(plan_published_at=Now(), plan_hidden=False) selected = queryset.values_list('pk', flat=True)
self.message_user(request, _('Plan published'), messages.SUCCESS) return HttpResponseRedirect(f"{reverse_lazy('admin:plan-publish')}?pks={','.join(str(pk) for pk in selected)}")
@action(description=_('Unpublish plan')) @action(description=_('Unpublish plan'))
def unpublish(self, request, queryset): def unpublish(self, request, queryset):
queryset.update(plan_published_at=None, plan_hidden=True) selected = queryset.values_list('pk', flat=True)
self.message_user(request, _('Plan unpublished'), messages.SUCCESS) return HttpResponseRedirect(f"{reverse_lazy('admin:plan-unpublish')}?pks={','.join(str(pk) for pk in selected)}")
@admin.register(AKOwner) @admin.register(AKOwner)
...@@ -185,24 +200,47 @@ class AKAdmin(SimpleHistoryAdmin): ...@@ -185,24 +200,47 @@ class AKAdmin(SimpleHistoryAdmin):
list_filter = ['event', WishFilter, ('category', EventRelatedFieldListFilter), ('requirements', EventRelatedFieldListFilter)] list_filter = ['event', WishFilter, ('category', EventRelatedFieldListFilter), ('requirements', EventRelatedFieldListFilter)]
list_editable = ['short_name', 'track', 'interest_counter'] list_editable = ['short_name', 'track', 'interest_counter']
ordering = ['pk'] ordering = ['pk']
actions = ['wiki_export'] actions = ['wiki_export', 'reset_interest', 'reset_interest_counter']
form = AKAdminForm form = AKAdminForm
@display(boolean=True)
def is_wish(self, obj): def is_wish(self, obj):
return obj.wish return obj.wish
@action(description=_("Export to wiki syntax"))
def wiki_export(self, request, queryset): def wiki_export(self, request, queryset):
return render(request, 'admin/AKModel/wiki_export.html', context={"AKs": queryset}) # Only export when all AKs belong to the same event
if queryset.values("event").distinct().count() == 1:
wiki_export.short_description = _("Export to wiki syntax") event = queryset.first().event
pks = set(ak.pk for ak in queryset.all())
is_wish.boolean = True categories_with_aks = event.get_categories_with_aks(wishes_seperately=False, filter=lambda ak: ak.pk in pks,
hide_empty_categories=True)
return render(request, 'admin/AKModel/wiki_export.html', context={"categories_with_aks": categories_with_aks})
self.message_user(request, _("Cannot export AKs from more than one event at the same time."), messages.ERROR)
def formfield_for_foreignkey(self, db_field, request, **kwargs): def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'event': if db_field.name == 'event':
kwargs['initial'] = Event.get_next_active() kwargs['initial'] = Event.get_next_active()
return super(AKAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) return super(AKAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
def get_urls(self):
urls = [
path('reset-interest/', AKResetInterestView.as_view(), name="ak-reset-interest"),
path('reset-interest-counter/', AKResetInterestCounterView.as_view(), name="ak-reset-interest-counter"),
]
urls.extend(super().get_urls())
return urls
@action(description=_("Reset interest in AKs"))
def reset_interest(self, request, queryset):
selected = queryset.values_list('pk', flat=True)
return HttpResponseRedirect(f"{reverse_lazy('admin:ak-reset-interest')}?pks={','.join(str(pk) for pk in selected)}")
@action(description=_("Reset AKs' interest counters"))
def reset_interest_counter(self, request, queryset):
selected = queryset.values_list('pk', flat=True)
return HttpResponseRedirect(f"{reverse_lazy('admin:ak-reset-interest-counter')}?pks={','.join(str(pk) for pk in selected)}")
class RoomForm(AvailabilitiesFormMixin, forms.ModelForm): class RoomForm(AvailabilitiesFormMixin, forms.ModelForm):
class Meta: class Meta:
...@@ -282,14 +320,13 @@ class AKSlotAdmin(admin.ModelAdmin): ...@@ -282,14 +320,13 @@ class AKSlotAdmin(admin.ModelAdmin):
kwargs['initial'] = Event.get_next_active() kwargs['initial'] = Event.get_next_active()
return super(AKSlotAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) return super(AKSlotAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
@display(description=_('AK Details'))
def ak_details_link(self, akslot): def ak_details_link(self, akslot):
if apps.is_installed("AKSubmission") and akslot.ak is not None: if apps.is_installed("AKSubmission") and akslot.ak is not None:
link = f"<a href={reverse('submit:ak_detail', args=[akslot.event.slug, akslot.ak.pk])}>{str(akslot.ak)}</a>" link = f"<a href={reverse('submit:ak_detail', args=[akslot.event.slug, akslot.ak.pk])}>{str(akslot.ak)}</a>"
return mark_safe(link) return mark_safe(link)
return "-" return "-"
ak_details_link.short_description = _('AK Details')
@admin.register(Availability) @admin.register(Availability)
class AvailabilityAdmin(admin.ModelAdmin): class AvailabilityAdmin(admin.ModelAdmin):
...@@ -325,7 +362,32 @@ class ConstraintViolationAdminForm(forms.ModelForm): ...@@ -325,7 +362,32 @@ class ConstraintViolationAdminForm(forms.ModelForm):
@admin.register(ConstraintViolation) @admin.register(ConstraintViolation)
class ConstraintViolationAdmin(admin.ModelAdmin): class ConstraintViolationAdmin(admin.ModelAdmin):
list_display = ['type', 'level', 'get_details'] list_display = ['type', 'level', 'get_details', 'manually_resolved']
list_filter = ['event'] list_filter = ['event']
readonly_fields = ['timestamp'] readonly_fields = ['timestamp']
form = ConstraintViolationAdminForm form = ConstraintViolationAdminForm
actions = ['mark_resolved', 'set_violation', 'set_warning']
def get_urls(self):
urls = [
path('mark-resolved/', CVMarkResolvedView.as_view(), name="cv-mark-resolved"),
path('set-violation/', CVSetLevelViolationView.as_view(), name="cv-set-violation"),
path('set-warning/', CVSetLevelWarningView.as_view(), name="cv-set-warning"),
]
urls.extend(super().get_urls())
return urls
@action(description=_("Mark Constraint Violations as manually resolved"))
def mark_resolved(self, request, queryset):
selected = queryset.values_list('pk', flat=True)
return HttpResponseRedirect(f"{reverse_lazy('admin:cv-mark-resolved')}?pks={','.join(str(pk) for pk in selected)}")
@action(description=_('Set Constraint Violations to level "violation"'))
def set_violation(self, request, queryset):
selected = queryset.values_list('pk', flat=True)
return HttpResponseRedirect(f"{reverse_lazy('admin:cv-set-violation')}?pks={','.join(str(pk) for pk in selected)}")
@action(description=_('Set Constraint Violations to level "warning"'))
def set_warning(self, request, queryset):
selected = queryset.values_list('pk', flat=True)
return HttpResponseRedirect(f"{reverse_lazy('admin:cv-set-warning')}?pks={','.join(str(pk) for pk in selected)}")
...@@ -73,3 +73,31 @@ class NewEventWizardActivateForm(forms.ModelForm): ...@@ -73,3 +73,31 @@ class NewEventWizardActivateForm(forms.ModelForm):
class AdminIntermediateForm(forms.Form): class AdminIntermediateForm(forms.Form):
pass pass
class AdminIntermediateActionForm(AdminIntermediateForm):
pks = forms.CharField(widget=forms.HiddenInput)
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?"))
This diff is collapsed.
...@@ -73,7 +73,7 @@ class Event(models.Model): ...@@ -73,7 +73,7 @@ class Event(models.Model):
event = Event.objects.order_by('start').filter(start__gt=datetime.now()).first() event = Event.objects.order_by('start').filter(start__gt=datetime.now()).first()
return event return event
def get_categories_with_aks(self, wishes_seperately=False, filter=lambda ak: True): def get_categories_with_aks(self, wishes_seperately=False, filter=lambda ak: True, hide_empty_categories=False):
""" """
Get AKCategories as well as a list of AKs belonging to the category for this event Get AKCategories as well as a list of AKs belonging to the category for this event
...@@ -97,6 +97,7 @@ class Event(models.Model): ...@@ -97,6 +97,7 @@ class Event(models.Model):
else: else:
if filter(ak): if filter(ak):
ak_list.append(ak) ak_list.append(ak)
if not hide_empty_categories or len(ak_list) > 0:
categories_with_aks.append((category, ak_list)) categories_with_aks.append((category, ak_list))
return categories_with_aks, ak_wishes return categories_with_aks, ak_wishes
else: else:
...@@ -105,6 +106,7 @@ class Event(models.Model): ...@@ -105,6 +106,7 @@ class Event(models.Model):
for ak in category.ak_set.all(): for ak in category.ak_set.all():
if filter(ak): if filter(ak):
ak_list.append(ak) ak_list.append(ak)
if not hide_empty_categories or len(ak_list) > 0:
categories_with_aks.append((category, ak_list)) categories_with_aks.append((category, ak_list))
return categories_with_aks return categories_with_aks
......
...@@ -11,6 +11,13 @@ ...@@ -11,6 +11,13 @@
<h2><a href="{% url 'admin:AKModel_event_change' event.pk %}">{{event}}</a></h2> <h2><a href="{% url 'admin:AKModel_event_change' event.pk %}">{{event}}</a></h2>
<h5>{{ event.start }} - {{ event.end }}</h5> <h5>{{ event.start }} - {{ event.end }}</h5>
<div class="custom-control custom-switch mt-2 mb-2">
<input type="checkbox" class="custom-control-input" id="planPublishedSwitch"
{% if not event.plan_hidden %}checked{% endif %}
onclick="location.href='{% if event.plan_hidden %}{% url 'admin:plan-publish' %}{% else %}{% url 'admin:plan-unpublish' %}{% endif %}?pks={{event.pk}}';">
<label class="custom-control-label" for="planPublishedSwitch">{% trans "Plan published?" %}</label>
</div>
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-8">
<h3 class="block-header">{% trans "Categories" %}</h3> <h3 class="block-header">{% trans "Categories" %}</h3>
...@@ -89,9 +96,7 @@ ...@@ -89,9 +96,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"),
] ]
import os
import tempfile
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
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.db.models.functions import Now
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, reverse
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 AdminIntermediateActionForm
from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement, \
ConstraintViolation
from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \ from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \
AKOwnerSerializer AKOwnerSerializer
...@@ -195,13 +199,12 @@ class AKWikiExportView(AdminViewMixin, DetailView): ...@@ -195,13 +199,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 +220,6 @@ class AKMessageDeleteView(EventSlugMixin, IntermediateAdminView): ...@@ -217,9 +220,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,15 +326,16 @@ class NewEventWizardFinishView(WizardViewMixin, DetailView): ...@@ -326,15 +326,16 @@ 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 NEXT_AK_LIST_LENGTH = form.cleaned_data['num_next']
RESULT_PRESENTATION_MODE = True if "presentation_mode" in request.GET else False RESULT_PRESENTATION_MODE = form.cleaned_data["presentation_mode"]
SPACE_FOR_NOTES_IN_WISHES = request.GET["wish_notes"] == "True" if "wish_notes" in request.GET else False SPACE_FOR_NOTES_IN_WISHES = form.cleaned_data["wish_notes"]
translations = { translations = {
'symbols': _("Symbols"), 'symbols': _("Symbols"),
...@@ -349,11 +350,11 @@ def export_slides(request, event_slug): ...@@ -349,11 +350,11 @@ def export_slides(request, event_slug):
next_aks_list = zip_longest(*[ak_list[i + 1:] for i in range(NEXT_AK_LIST_LENGTH)], fillvalue=None) 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())] 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 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))) ak: not RESULT_PRESENTATION_MODE or (ak.present or (ak.present is None and ak.category.present_by_default)))
context = { context = {
'title': event.name, 'title': self.event.name,
'categories_with_aks': [(category, build_ak_list_with_next_aks(ak_list)) for category, ak_list in 'categories_with_aks': [(category, build_ak_list_with_next_aks(ak_list)) for category, ak_list in
categories_with_aks], categories_with_aks],
'subtitle': _("AKs"), 'subtitle': _("AKs"),
...@@ -363,4 +364,134 @@ def export_slides(request, event_slug): ...@@ -363,4 +364,134 @@ def export_slides(request, event_slug):
"space_for_notes_in_wishes": SPACE_FOR_NOTES_IN_WISHES, "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')
class IntermediateAdminActionView(IntermediateAdminView, ABC):
form_class = AdminIntermediateActionForm
entities = None
def get_queryset(self, pks=None):
if pks is None:
pks = self.request.GET['pks']
return self.model.objects.filter(pk__in=pks.split(","))
def get_initial(self):
initial = super().get_initial()
initial['pks'] = self.request.GET['pks']
return initial
def get_preview(self):
self.entities = self.get_queryset()
joined_entities = '\n'.join(str(e) for e in self.entities)
return f"{self.confirmation_message}:\n\n {joined_entities}"
def get_success_url(self):
return reverse(f"admin:{self.model._meta.app_label}_{self.model._meta.model_name}_changelist")
@abstractmethod
def action(self, form):
pass
def form_valid(self, form):
self.entities = self.get_queryset(pks=form.cleaned_data['pks'])
self.action(form)
messages.add_message(self.request, messages.SUCCESS, self.success_message)
return super().form_valid(form)
class LoopActionMixin(ABC):
def action(self, form):
self.pre_action()
for entity in self.entities:
self.perform_action(entity)
entity.save()
self.post_action()
@abstractmethod
def perform_action(self, entity):
pass
def pre_action(self):
pass
def post_action(self):
pass
class CVMarkResolvedView(IntermediateAdminActionView):
title = _('Mark Constraint Violations as manually resolved')
model = ConstraintViolation
confirmation_message = _("The following Constraint Violations will be marked as manually resolved")
success_message = _("Constraint Violations marked as resolved")
def action(self, form):
self.entities.update(manually_resolved=True)
class CVSetLevelViolationView(IntermediateAdminActionView):
title = _('Set Constraint Violations to level "violation"')
model = ConstraintViolation
confirmation_message = _("The following Constraint Violations will be set to level 'violation'")
success_message = _("Constraint Violations set to level 'violation'")
def action(self, form):
self.entities.update(level=ConstraintViolation.ViolationLevel.VIOLATION)
class CVSetLevelWarningView(IntermediateAdminActionView):
title = _('Set Constraint Violations to level "warning"')
model = ConstraintViolation
confirmation_message = _("The following Constraint Violations will be set to level 'warning'")
success_message = _("Constraint Violations set to level 'warning'")
def action(self, form):
self.entities.update(level=ConstraintViolation.ViolationLevel.WARNING)
class AKResetInterestView(IntermediateAdminActionView):
title = _("Reset interest in AKs")
model = AK
confirmation_message = _("Interest of the following AKs will be set to not filled (-1):")
success_message = _("Reset of interest in AKs successful.")
def action(self, form):
self.entities.update(interest=-1)
class AKResetInterestCounterView(IntermediateAdminActionView):
title = _("Reset AKs' interest counters")
model = AK
confirmation_message = _("Interest counter of the following AKs will be set to 0:")
success_message = _("AKs' interest counters set back to 0.")
def action(self, form):
self.entities.update(interest_counter=0)
class PlanPublishView(IntermediateAdminActionView):
title = _('Publish plan')
model = Event
confirmation_message = _('Publish the plan(s) of:')
success_message = _('Plan published')
def action(self, form):
self.entities.update(plan_published_at=Now(), plan_hidden=False)
class PlanUnpublishView(IntermediateAdminActionView):
title = _('Unpublish plan')
model = Event
confirmation_message = _('Unpublish the plan(s) of:')
success_message = _('Plan unpublished')
def action(self, form):
self.entities.update(plan_published_at=None, plan_hidden=True)
...@@ -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.
Please register or to comment