diff --git a/AKModel/admin.py b/AKModel/admin.py index 39fc678db42611d3b2192a006179a46524805f6a..6bbbee0f960ba462eff2ff7d1ebeb943cf3fc790 100644 --- a/AKModel/admin.py +++ b/AKModel/admin.py @@ -1,11 +1,11 @@ from django import forms from django.apps import apps 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.functions import Now +from django.http import HttpResponseRedirect 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.html import format_html from django.utils.safestring import mark_safe @@ -18,6 +18,8 @@ from AKModel.availability.models import Availability from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \ ConstraintViolation 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): @@ -36,7 +38,7 @@ class EventAdmin(admin.ModelAdmin): list_filter = ['active'] list_editable = ['active'] 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'] def add_view(self, request, form_url='', extra_context=None): @@ -50,14 +52,27 @@ class EventAdmin(admin.ModelAdmin): if apps.is_installed("AKScheduling"): from AKScheduling.urls import get_admin_urls_scheduling 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()) return urls + @display(description=_("Status")) def status_url(self, obj): return format_html("<a href='{url}'>{text}</a>", 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): # Use timezone of event @@ -66,13 +81,13 @@ class EventAdmin(admin.ModelAdmin): @action(description=_('Publish plan')) def publish(self, request, queryset): - queryset.update(plan_published_at=Now(), plan_hidden=False) - self.message_user(request, _('Plan published'), messages.SUCCESS) + selected = queryset.values_list('pk', flat=True) + return HttpResponseRedirect(f"{reverse_lazy('admin:plan-publish')}?pks={','.join(str(pk) for pk in selected)}") @action(description=_('Unpublish plan')) def unpublish(self, request, queryset): - queryset.update(plan_published_at=None, plan_hidden=True) - self.message_user(request, _('Plan unpublished'), messages.SUCCESS) + selected = queryset.values_list('pk', flat=True) + return HttpResponseRedirect(f"{reverse_lazy('admin:plan-unpublish')}?pks={','.join(str(pk) for pk in selected)}") @admin.register(AKOwner) @@ -185,24 +200,47 @@ class AKAdmin(SimpleHistoryAdmin): list_filter = ['event', WishFilter, ('category', EventRelatedFieldListFilter), ('requirements', EventRelatedFieldListFilter)] list_editable = ['short_name', 'track', 'interest_counter'] ordering = ['pk'] - actions = ['wiki_export'] + actions = ['wiki_export', 'reset_interest', 'reset_interest_counter'] form = AKAdminForm + @display(boolean=True) def is_wish(self, obj): return obj.wish + @action(description=_("Export to wiki syntax")) def wiki_export(self, request, queryset): - return render(request, 'admin/AKModel/wiki_export.html', context={"AKs": queryset}) - - wiki_export.short_description = _("Export to wiki syntax") - - is_wish.boolean = True + # Only export when all AKs belong to the same event + if queryset.values("event").distinct().count() == 1: + event = queryset.first().event + pks = set(ak.pk for ak in queryset.all()) + 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): if db_field.name == 'event': kwargs['initial'] = Event.get_next_active() 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 Meta: @@ -282,14 +320,13 @@ class AKSlotAdmin(admin.ModelAdmin): kwargs['initial'] = Event.get_next_active() return super(AKSlotAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) + @display(description=_('AK Details')) def ak_details_link(self, akslot): 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>" return mark_safe(link) return "-" - ak_details_link.short_description = _('AK Details') - @admin.register(Availability) class AvailabilityAdmin(admin.ModelAdmin): @@ -325,7 +362,32 @@ class ConstraintViolationAdminForm(forms.ModelForm): @admin.register(ConstraintViolation) class ConstraintViolationAdmin(admin.ModelAdmin): - list_display = ['type', 'level', 'get_details'] + list_display = ['type', 'level', 'get_details', 'manually_resolved'] list_filter = ['event'] readonly_fields = ['timestamp'] 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)}") diff --git a/AKModel/forms.py b/AKModel/forms.py index 43f712ed9614a6727abd8488620c7d1db953307d..3aa9486813bc9fb91ee21860fc8614bb0ff96cfa 100644 --- a/AKModel/forms.py +++ b/AKModel/forms.py @@ -73,3 +73,31 @@ class NewEventWizardActivateForm(forms.ModelForm): class AdminIntermediateForm(forms.Form): 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?")) diff --git a/AKModel/locale/de_DE/LC_MESSAGES/django.po b/AKModel/locale/de_DE/LC_MESSAGES/django.po index fd79e07e6c2dd0a314188752a2e4a9bebba12300..0853138780d82c17b9c73d2f07a73870706d0e85 100644 --- a/AKModel/locale/de_DE/LC_MESSAGES/django.po +++ b/AKModel/locale/de_DE/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-23 18:03+0000\n" +"POT-Creation-Date: 2022-10-24 00:20+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -11,7 +11,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: AKModel/admin.py:58 AKModel/admin.py:60 +#: AKModel/admin.py:62 AKModel/admin.py:65 #: AKModel/templates/admin/AKModel/event_wizard/activate.html:32 #: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:48 #: AKModel/templates/admin/AKModel/event_wizard/finish.html:21 @@ -22,41 +22,61 @@ msgid "Status" msgstr "Status" #: AKModel/admin.py:67 +msgid "Toggle plan visibility" +msgstr "Plansichtbarkeit ändern" + +#: AKModel/admin.py:71 AKModel/admin.py:82 AKModel/views.py:481 msgid "Publish plan" msgstr "Plan veröffentlichen" -#: AKModel/admin.py:70 -msgid "Plan published" -msgstr "Plan veröffentlicht" - -#: AKModel/admin.py:72 +#: AKModel/admin.py:74 AKModel/admin.py:87 AKModel/views.py:491 msgid "Unpublish plan" msgstr "Plan verbergen" -#: AKModel/admin.py:75 -msgid "Plan unpublished" -msgstr "Plan verborgen" - -#: AKModel/admin.py:144 +#: AKModel/admin.py:159 msgid "Wish" msgstr "AK-Wunsch" -#: AKModel/admin.py:150 +#: AKModel/admin.py:165 msgid "Is wish" msgstr "Ist ein Wunsch" -#: AKModel/admin.py:151 +#: AKModel/admin.py:166 msgid "Is not a wish" msgstr "Ist kein Wunsch" -#: AKModel/admin.py:197 +#: AKModel/admin.py:210 msgid "Export to wiki syntax" msgstr "In Wiki-Syntax exportieren" -#: AKModel/admin.py:291 +#: AKModel/admin.py:219 +msgid "Cannot export AKs from more than one event at the same time." +msgstr "Kann nicht AKs von mehreren Events zur selben Zeit exportieren." + +#: AKModel/admin.py:234 AKModel/views.py:461 +msgid "Reset interest in AKs" +msgstr "Interesse an AKs zurücksetzen" + +#: AKModel/admin.py:239 AKModel/views.py:471 +msgid "Reset AKs' interest counters" +msgstr "Interessenszähler der AKs zurücksetzen" + +#: AKModel/admin.py:323 msgid "AK Details" msgstr "AK-Details" +#: AKModel/admin.py:380 AKModel/views.py:431 +msgid "Mark Constraint Violations as manually resolved" +msgstr "Markiere Constraintverletzungen als manuell behoben" + +#: AKModel/admin.py:385 AKModel/views.py:441 +msgid "Set Constraint Violations to level \"violation\"" +msgstr "Constraintverletzungen auf Level \"Violation\" setzen" + +#: AKModel/admin.py:390 AKModel/views.py:451 +msgid "Set Constraint Violations to level \"warning\"" +msgstr "Constraintverletzungen auf Level \"Warning\" setzen" + #: AKModel/availability/forms.py:21 AKModel/availability/models.py:248 msgid "Availability" msgstr "Verfügbarkeit" @@ -81,17 +101,17 @@ msgstr "Die eingegebene Verfügbarkeit enthält ein ungültiges Datum." msgid "Please fill in your availabilities!" msgstr "Bitte Verfügbarkeiten eintragen!" -#: AKModel/availability/models.py:38 AKModel/models.py:56 AKModel/models.py:126 -#: AKModel/models.py:181 AKModel/models.py:200 AKModel/models.py:232 -#: AKModel/models.py:286 AKModel/models.py:352 AKModel/models.py:385 -#: AKModel/models.py:456 AKModel/models.py:497 +#: AKModel/availability/models.py:38 AKModel/models.py:56 AKModel/models.py:128 +#: AKModel/models.py:183 AKModel/models.py:202 AKModel/models.py:234 +#: AKModel/models.py:288 AKModel/models.py:354 AKModel/models.py:387 +#: AKModel/models.py:458 AKModel/models.py:499 msgid "Event" msgstr "Event" -#: AKModel/availability/models.py:39 AKModel/models.py:127 -#: AKModel/models.py:182 AKModel/models.py:201 AKModel/models.py:233 -#: AKModel/models.py:287 AKModel/models.py:353 AKModel/models.py:386 -#: AKModel/models.py:457 AKModel/models.py:498 +#: AKModel/availability/models.py:39 AKModel/models.py:129 +#: AKModel/models.py:184 AKModel/models.py:203 AKModel/models.py:235 +#: AKModel/models.py:289 AKModel/models.py:355 AKModel/models.py:388 +#: AKModel/models.py:459 AKModel/models.py:500 msgid "Associated event" msgstr "Zugehöriges Event" @@ -103,8 +123,8 @@ msgstr "Person" msgid "Person whose availability this is" msgstr "Person deren Verfügbarkeit hier abgebildet wird" -#: AKModel/availability/models.py:56 AKModel/models.py:356 -#: AKModel/models.py:375 AKModel/models.py:506 +#: AKModel/availability/models.py:56 AKModel/models.py:358 +#: AKModel/models.py:377 AKModel/models.py:508 msgid "Room" msgstr "Raum" @@ -112,8 +132,8 @@ msgstr "Raum" msgid "Room whose availability this is" msgstr "Raum dessen Verfügbarkeit hier abgebildet wird" -#: AKModel/availability/models.py:65 AKModel/models.py:292 -#: AKModel/models.py:374 AKModel/models.py:451 +#: AKModel/availability/models.py:65 AKModel/models.py:294 +#: AKModel/models.py:376 AKModel/models.py:453 msgid "AK" msgstr "AK" @@ -121,8 +141,8 @@ msgstr "AK" msgid "AK whose availability this is" msgstr "Verfügbarkeiten" -#: AKModel/availability/models.py:74 AKModel/models.py:185 -#: AKModel/models.py:512 +#: AKModel/availability/models.py:74 AKModel/models.py:187 +#: AKModel/models.py:514 msgid "AK Category" msgstr "AK-Kategorie" @@ -151,9 +171,46 @@ msgstr "AK-Kategorien kopieren" msgid "Copy ak requirements" msgstr "AK-Anforderungen kopieren" -#: AKModel/models.py:18 AKModel/models.py:173 AKModel/models.py:197 -#: AKModel/models.py:216 AKModel/models.py:230 AKModel/models.py:248 -#: AKModel/models.py:344 +#: AKModel/forms.py:87 +msgid "# next AKs" +msgstr "# nächste AKs" + +#: AKModel/forms.py:88 +msgid "How many next AKs should be shown on a slide?" +msgstr "Wie viele nächste AKs sollen auf einer Folie angezeigt werden?" + +#: AKModel/forms.py:91 +msgid "Presentation only?" +msgstr "Nur Vorstellung?" + +#: AKModel/forms.py:93 AKModel/forms.py:100 +msgid "Yes" +msgstr "Ja" + +#: AKModel/forms.py:93 AKModel/forms.py:100 +msgid "No" +msgstr "Nein" + +#: AKModel/forms.py:95 +msgid "Restrict AKs to those that asked for chance to be presented?" +msgstr "AKs auf solche, die um eine Vorstellung gebeten haben, einschränken?" + +#: AKModel/forms.py:98 +msgid "Space for notes in wishes?" +msgstr "Platz für Notizen bei den Wünschen?" + +#: AKModel/forms.py:102 +msgid "" +"Create symbols indicating space to note down owners and timeslots for " +"wishes, e.g., to be filled out on a touch screen while presenting?" +msgstr "" +"Symbole anlegen, die Raum zum Notieren von Leitungen und Zeitslots " +"fürWünsche markieren, z.B. um während der Präsentation auf einem Touchscreen " +"ausgefüllt zu werden?" + +#: AKModel/models.py:18 AKModel/models.py:175 AKModel/models.py:199 +#: AKModel/models.py:218 AKModel/models.py:232 AKModel/models.py:250 +#: AKModel/models.py:346 msgid "Name" msgstr "Name" @@ -295,71 +352,71 @@ msgstr "" msgid "Events" msgstr "Events" -#: AKModel/models.py:121 +#: AKModel/models.py:123 msgid "Nickname" msgstr "Spitzname" -#: AKModel/models.py:121 +#: AKModel/models.py:123 msgid "Name to identify an AK owner by" msgstr "Name, durch den eine AK-Leitung identifiziert wird" -#: AKModel/models.py:122 +#: AKModel/models.py:124 msgid "Slug" msgstr "Slug" -#: AKModel/models.py:122 +#: AKModel/models.py:124 msgid "Slug for URL generation" msgstr "Slug für URL-Generierung" -#: AKModel/models.py:123 +#: AKModel/models.py:125 msgid "Institution" msgstr "Instutution" -#: AKModel/models.py:123 +#: AKModel/models.py:125 msgid "Uni etc." msgstr "Universität o.ä." -#: AKModel/models.py:124 AKModel/models.py:257 +#: AKModel/models.py:126 AKModel/models.py:259 msgid "Web Link" msgstr "Internet Link" -#: AKModel/models.py:124 +#: AKModel/models.py:126 msgid "Link to Homepage" msgstr "Link zu Homepage oder Webseite" -#: AKModel/models.py:130 AKModel/models.py:505 +#: AKModel/models.py:132 AKModel/models.py:507 msgid "AK Owner" msgstr "AK-Leitung" -#: AKModel/models.py:131 +#: AKModel/models.py:133 msgid "AK Owners" msgstr "AK-Leitungen" -#: AKModel/models.py:173 +#: AKModel/models.py:175 msgid "Name of the AK Category" msgstr "Name der AK-Kategorie" -#: AKModel/models.py:174 AKModel/models.py:198 +#: AKModel/models.py:176 AKModel/models.py:200 msgid "Color" msgstr "Farbe" -#: AKModel/models.py:174 AKModel/models.py:198 +#: AKModel/models.py:176 AKModel/models.py:200 msgid "Color for displaying" msgstr "Farbe für die Anzeige" -#: AKModel/models.py:175 AKModel/models.py:251 +#: AKModel/models.py:177 AKModel/models.py:253 msgid "Description" msgstr "Beschreibung" -#: AKModel/models.py:176 +#: AKModel/models.py:178 msgid "Short description of this AK Category" msgstr "Beschreibung der AK-Kategorie" -#: AKModel/models.py:177 +#: AKModel/models.py:179 msgid "Present by default" msgstr "Defaultmäßig präsentieren" -#: AKModel/models.py:179 +#: AKModel/models.py:181 msgid "" "Present AKs of this category by default if AK owner did not specify whether " "this AK should be presented?" @@ -367,156 +424,152 @@ msgstr "" "AKs dieser Kategorie standardmäßig vorstellen, wenn die Leitungen das für " "ihren AK nicht explizit spezifiziert haben?" -#: AKModel/models.py:186 +#: AKModel/models.py:188 msgid "AK Categories" msgstr "AK-Kategorien" -#: AKModel/models.py:197 +#: AKModel/models.py:199 msgid "Name of the AK Track" msgstr "Name des AK-Tracks" -#: AKModel/models.py:204 +#: AKModel/models.py:206 msgid "AK Track" msgstr "AK-Track" -#: AKModel/models.py:205 +#: AKModel/models.py:207 msgid "AK Tracks" msgstr "AK-Tracks" -#: AKModel/models.py:216 +#: AKModel/models.py:218 msgid "Name of the AK Tag" msgstr "Name das AK-Tags" -#: AKModel/models.py:219 +#: AKModel/models.py:221 msgid "AK Tag" msgstr "AK-Tag" -#: AKModel/models.py:220 +#: AKModel/models.py:222 msgid "AK Tags" msgstr "AK-Tags" -#: AKModel/models.py:230 +#: AKModel/models.py:232 msgid "Name of the Requirement" msgstr "Name der Anforderung" -#: AKModel/models.py:236 AKModel/models.py:509 +#: AKModel/models.py:238 AKModel/models.py:511 msgid "AK Requirement" msgstr "AK-Anforderung" -#: AKModel/models.py:237 +#: AKModel/models.py:239 msgid "AK Requirements" msgstr "AK-Anforderungen" -#: AKModel/models.py:248 +#: AKModel/models.py:250 msgid "Name of the AK" msgstr "Name des AKs" -#: AKModel/models.py:249 +#: AKModel/models.py:251 msgid "Short Name" msgstr "Kurzer Name" -#: AKModel/models.py:250 +#: AKModel/models.py:252 msgid "Name displayed in the schedule" msgstr "Name zur Anzeige im AK-Plan" -#: AKModel/models.py:251 +#: AKModel/models.py:253 msgid "Description of the AK" msgstr "Beschreibung des AKs" -#: AKModel/models.py:253 +#: AKModel/models.py:255 msgid "Owners" msgstr "Leitungen" -#: AKModel/models.py:254 +#: AKModel/models.py:256 msgid "Those organizing the AK" msgstr "Menschen, die den AK organisieren und halten" -#: AKModel/models.py:257 +#: AKModel/models.py:259 msgid "Link to wiki page" msgstr "Link zur Wiki Seite" -#: AKModel/models.py:258 +#: AKModel/models.py:260 msgid "Protocol Link" msgstr "Protokolllink" -#: AKModel/models.py:258 +#: AKModel/models.py:260 msgid "Link to protocol" msgstr "Link zum Protokoll" -#: AKModel/models.py:260 +#: AKModel/models.py:262 msgid "Category" msgstr "Kategorie" -#: AKModel/models.py:261 +#: AKModel/models.py:263 msgid "Category of the AK" msgstr "Kategorie des AKs" -#: AKModel/models.py:262 +#: AKModel/models.py:264 msgid "Tags" msgstr "Tags" -#: AKModel/models.py:262 +#: AKModel/models.py:264 msgid "Tags provided by owners" msgstr "Tags, die durch die AK-Leitung vergeben wurden" -#: AKModel/models.py:263 +#: AKModel/models.py:265 msgid "Track" msgstr "Track" -#: AKModel/models.py:264 +#: AKModel/models.py:266 msgid "Track the AK belongs to" msgstr "Track zu dem der AK gehört" -#: AKModel/models.py:266 +#: AKModel/models.py:268 msgid "Resolution Intention" msgstr "Resolutionsabsicht" -#: AKModel/models.py:267 +#: AKModel/models.py:269 msgid "Intends to submit a resolution" msgstr "Beabsichtigt eine Resolution einzureichen" -#: AKModel/models.py:268 +#: AKModel/models.py:270 msgid "Present this AK" msgstr "AK präsentieren" -#: AKModel/models.py:269 +#: AKModel/models.py:271 msgid "Present results of this AK" msgstr "Die Ergebnisse dieses AKs vorstellen" -#: AKModel/models.py:271 AKModel/templates/admin/AKModel/status.html:97 +#: AKModel/models.py:273 AKModel/templates/admin/AKModel/status.html:102 msgid "Requirements" msgstr "Anforderungen" -#: AKModel/models.py:272 +#: AKModel/models.py:274 msgid "AK's Requirements" msgstr "Anforderungen des AKs" -#: AKModel/models.py:274 +#: AKModel/models.py:276 msgid "Conflicting AKs" msgstr "AK-Konflikte" -#: AKModel/models.py:275 +#: AKModel/models.py:277 msgid "AKs that conflict and thus must not take place at the same time" msgstr "" "AKs, die Konflikte haben und deshalb nicht gleichzeitig stattfinden dürfen" -#: AKModel/models.py:276 +#: AKModel/models.py:278 msgid "Prerequisite AKs" msgstr "Vorausgesetzte AKs" -#: AKModel/models.py:277 +#: AKModel/models.py:279 msgid "AKs that should precede this AK in the schedule" msgstr "AKs die im AK-Plan vor diesem AK stattfinden müssen" -#: AKModel/models.py:279 +#: AKModel/models.py:281 msgid "Organizational Notes" msgstr "Notizen zur Organisation" -#: AKModel/models.py:280 -#, fuzzy -#| msgid "" -#| "Notes to organizers. These are public. For private notes, please send an " -#| "e-mail." +#: AKModel/models.py:282 msgid "" "Notes to organizers. These are public. For private notes, please use the " "button for private messages on the detail page of this AK (after creation/" @@ -526,258 +579,258 @@ msgstr "" "Anmerkungen bitte den Button für Direktnachrichten verwenden (nach dem " "Anlegen/Bearbeiten)." -#: AKModel/models.py:282 +#: AKModel/models.py:284 msgid "Interest" msgstr "Interesse" -#: AKModel/models.py:282 +#: AKModel/models.py:284 msgid "Expected number of people" msgstr "Erwartete Personenzahl" -#: AKModel/models.py:283 +#: AKModel/models.py:285 msgid "Interest Counter" msgstr "Interessenszähler" -#: AKModel/models.py:284 +#: AKModel/models.py:286 msgid "People who have indicated interest online" msgstr "Anzahl Personen, die online Interesse bekundet haben" -#: AKModel/models.py:293 AKModel/models.py:500 -#: AKModel/templates/admin/AKModel/status.html:49 -#: AKModel/templates/admin/AKModel/status.html:56 AKModel/views.py:359 +#: AKModel/models.py:295 AKModel/models.py:502 +#: AKModel/templates/admin/AKModel/status.html:56 +#: AKModel/templates/admin/AKModel/status.html:63 AKModel/views.py:360 msgid "AKs" msgstr "AKs" -#: AKModel/models.py:344 +#: AKModel/models.py:346 msgid "Name or number of the room" msgstr "Name oder Nummer des Raums" -#: AKModel/models.py:345 +#: AKModel/models.py:347 msgid "Location" msgstr "Ort" -#: AKModel/models.py:346 +#: AKModel/models.py:348 msgid "Name or number of the location" msgstr "Name oder Nummer des Ortes" -#: AKModel/models.py:347 +#: AKModel/models.py:349 msgid "Capacity" msgstr "Kapazität" -#: AKModel/models.py:348 +#: AKModel/models.py:350 msgid "Maximum number of people (-1 for unlimited)." msgstr "Maximale Personenzahl (-1 wenn unbeschränkt)." -#: AKModel/models.py:349 +#: AKModel/models.py:351 msgid "Properties" msgstr "Eigenschaften" -#: AKModel/models.py:350 +#: AKModel/models.py:352 msgid "AK requirements fulfilled by the room" msgstr "AK-Anforderungen, die dieser Raum erfüllt" -#: AKModel/models.py:357 AKModel/templates/admin/AKModel/status.html:33 +#: AKModel/models.py:359 AKModel/templates/admin/AKModel/status.html:40 msgid "Rooms" msgstr "Räume" -#: AKModel/models.py:374 +#: AKModel/models.py:376 msgid "AK being mapped" msgstr "AK, der zugeordnet wird" -#: AKModel/models.py:376 +#: AKModel/models.py:378 msgid "Room the AK will take place in" msgstr "Raum in dem der AK stattfindet" -#: AKModel/models.py:377 +#: AKModel/models.py:379 msgid "Slot Begin" msgstr "Beginn des Slots" -#: AKModel/models.py:377 +#: AKModel/models.py:379 msgid "Time and date the slot begins" msgstr "Zeit und Datum zu der der AK beginnt" -#: AKModel/models.py:379 +#: AKModel/models.py:381 msgid "Duration" msgstr "Dauer" -#: AKModel/models.py:380 +#: AKModel/models.py:382 msgid "Length in hours" msgstr "Länge in Stunden" -#: AKModel/models.py:382 +#: AKModel/models.py:384 msgid "Scheduling fixed" msgstr "Planung fix" -#: AKModel/models.py:383 +#: AKModel/models.py:385 msgid "Length and time of this AK should not be changed" msgstr "Dauer und Zeit dieses AKs sollten nicht verändert werden" -#: AKModel/models.py:388 +#: AKModel/models.py:390 msgid "Last update" msgstr "Letzte Aktualisierung" -#: AKModel/models.py:391 +#: AKModel/models.py:393 msgid "AK Slot" msgstr "AK-Slot" -#: AKModel/models.py:392 AKModel/models.py:502 +#: AKModel/models.py:394 AKModel/models.py:504 msgid "AK Slots" msgstr "AK-Slot" -#: AKModel/models.py:414 AKModel/models.py:423 +#: AKModel/models.py:416 AKModel/models.py:425 msgid "Not scheduled yet" msgstr "Noch nicht geplant" -#: AKModel/models.py:452 +#: AKModel/models.py:454 msgid "AK this message belongs to" msgstr "AK zu dem die Nachricht gehört" -#: AKModel/models.py:453 +#: AKModel/models.py:455 msgid "Message text" msgstr "Nachrichtentext" -#: AKModel/models.py:454 +#: AKModel/models.py:456 msgid "Message to the organizers. This is not publicly visible." msgstr "" "Nachricht an die Organisator*innen. Diese ist nicht öffentlich sichtbar." -#: AKModel/models.py:460 +#: AKModel/models.py:462 msgid "AK Orga Message" msgstr "AK-Organachricht" -#: AKModel/models.py:461 +#: AKModel/models.py:463 msgid "AK Orga Messages" msgstr "AK-Organachrichten" -#: AKModel/models.py:470 +#: AKModel/models.py:472 msgid "Constraint Violation" msgstr "Constraintverletzung" -#: AKModel/models.py:471 AKModel/templates/admin/AKModel/status.html:79 +#: AKModel/models.py:473 AKModel/templates/admin/AKModel/status.html:86 msgid "Constraint Violations" msgstr "Constraintverletzungen" -#: AKModel/models.py:475 +#: AKModel/models.py:477 msgid "Owner has two parallel slots" msgstr "Leitung hat zwei Slots parallel" -#: AKModel/models.py:476 +#: AKModel/models.py:478 msgid "AK Slot was scheduled outside the AK's availabilities" msgstr "AK Slot wurde außerhalb der Verfügbarkeit des AKs platziert" -#: AKModel/models.py:477 +#: AKModel/models.py:479 msgid "Room has two AK slots scheduled at the same time" msgstr "Raum hat zwei AK Slots gleichzeitig" -#: AKModel/models.py:478 +#: AKModel/models.py:480 msgid "Room does not satisfy the requirement of the scheduled AK" msgstr "Room erfüllt die Anforderungen des platzierten AKs nicht" -#: AKModel/models.py:479 +#: AKModel/models.py:481 msgid "AK Slot is scheduled at the same time as an AK listed as a conflict" msgstr "" "AK Slot wurde wurde zur gleichen Zeit wie ein Konflikt des AKs platziert" -#: AKModel/models.py:480 +#: AKModel/models.py:482 msgid "AK Slot is scheduled before an AK listed as a prerequisite" msgstr "AK Slot wurde vor einem als Voraussetzung gelisteten AK platziert" -#: AKModel/models.py:482 +#: AKModel/models.py:484 msgid "" "AK Slot for AK with intention to submit a resolution is scheduled after " "resolution deadline" msgstr "" "AK Slot eines AKs mit Resoabsicht wurde nach der Resodeadline platziert" -#: AKModel/models.py:483 +#: AKModel/models.py:485 msgid "AK Slot in a category is outside that categories availabilities" msgstr "AK Slot wurde außerhalb der Verfügbarkeiten seiner Kategorie" -#: AKModel/models.py:484 +#: AKModel/models.py:486 msgid "Two AK Slots for the same AK scheduled at the same time" msgstr "Zwei AK Slots eines AKs wurden zur selben Zeit platziert" -#: AKModel/models.py:485 +#: AKModel/models.py:487 msgid "Room does not have enough space for interest in scheduled AK Slot" msgstr "Room hat nicht genug Platz für das Interesse am geplanten AK-Slot" -#: AKModel/models.py:486 +#: AKModel/models.py:488 msgid "AK Slot is scheduled outside the event's availabilities" msgstr "AK Slot wurde außerhalb der Verfügbarkeit des Events platziert" -#: AKModel/models.py:489 +#: AKModel/models.py:491 msgid "Warning" msgstr "Warnung" -#: AKModel/models.py:490 +#: AKModel/models.py:492 msgid "Violation" msgstr "Verletzung" -#: AKModel/models.py:492 +#: AKModel/models.py:494 msgid "Type" msgstr "Art" -#: AKModel/models.py:493 +#: AKModel/models.py:495 msgid "Type of violation, i.e. what kind of constraint was violated" msgstr "Art der Verletzung, gibt an welche Art Constraint verletzt wurde" -#: AKModel/models.py:494 +#: AKModel/models.py:496 msgid "Level" msgstr "Level" -#: AKModel/models.py:495 +#: AKModel/models.py:497 msgid "Severity level of the violation" msgstr "Schweregrad der Verletzung" -#: AKModel/models.py:501 +#: AKModel/models.py:503 msgid "AK(s) belonging to this constraint" msgstr "AK(s), die zu diesem Constraint gehören" -#: AKModel/models.py:503 +#: AKModel/models.py:505 msgid "AK Slot(s) belonging to this constraint" msgstr "AK Slot(s), die zu diesem Constraint gehören" -#: AKModel/models.py:505 +#: AKModel/models.py:507 msgid "AK Owner belonging to this constraint" msgstr "AK Leitung(en), die zu diesem Constraint gehören" -#: AKModel/models.py:507 +#: AKModel/models.py:509 msgid "Room belonging to this constraint" msgstr "Raum, der zu diesem Constraint gehört" -#: AKModel/models.py:510 +#: AKModel/models.py:512 msgid "AK Requirement belonging to this constraint" msgstr "AK Anforderung, die zu diesem Constraint gehört" -#: AKModel/models.py:512 +#: AKModel/models.py:514 msgid "AK Category belonging to this constraint" msgstr "AK Kategorie, di zu diesem Constraint gehört" -#: AKModel/models.py:514 +#: AKModel/models.py:516 msgid "Comment" msgstr "Kommentar" -#: AKModel/models.py:514 +#: AKModel/models.py:516 msgid "Comment or further details for this violation" msgstr "Kommentar oder weitere Details zu dieser Vereletzung" -#: AKModel/models.py:517 +#: AKModel/models.py:519 msgid "Timestamp" msgstr "Timestamp" -#: AKModel/models.py:517 +#: AKModel/models.py:519 msgid "Time of creation" msgstr "Zeitpunkt der ERstellung" -#: AKModel/models.py:518 +#: AKModel/models.py:520 msgid "Manually Resolved" msgstr "Manuell behoben" -#: AKModel/models.py:519 +#: AKModel/models.py:521 msgid "Mark this violation manually as resolved" msgstr "Markiere diese Verletzung manuell als behoben" -#: AKModel/models.py:546 +#: AKModel/models.py:548 #: AKModel/templates/admin/AKModel/requirements_overview.html:27 msgid "Details" msgstr "Details" @@ -894,88 +947,88 @@ msgid "No AKs with this requirement" msgstr "Kein AK mit dieser Anforderung" #: AKModel/templates/admin/AKModel/requirements_overview.html:45 -#: AKModel/templates/admin/AKModel/status.html:113 +#: AKModel/templates/admin/AKModel/status.html:118 msgid "Add Requirement" msgstr "Anforderung hinzufügen" -#: AKModel/templates/admin/AKModel/status.html:16 +#: AKModel/templates/admin/AKModel/status.html:18 +msgid "Plan published?" +msgstr "Plan veröffentlicht?" + +#: AKModel/templates/admin/AKModel/status.html:23 msgid "Categories" msgstr "Kategorien" -#: AKModel/templates/admin/AKModel/status.html:18 +#: AKModel/templates/admin/AKModel/status.html:25 msgid "No categories yet" msgstr "Bisher keine Kategorien" -#: AKModel/templates/admin/AKModel/status.html:31 +#: AKModel/templates/admin/AKModel/status.html:38 msgid "Add category" msgstr "Kategorie hinzufügen" -#: AKModel/templates/admin/AKModel/status.html:35 +#: AKModel/templates/admin/AKModel/status.html:42 msgid "No rooms yet" msgstr "Bisher keine Räume" -#: AKModel/templates/admin/AKModel/status.html:47 +#: AKModel/templates/admin/AKModel/status.html:54 msgid "Add Room" msgstr "Raum hinzufügen" -#: AKModel/templates/admin/AKModel/status.html:51 +#: AKModel/templates/admin/AKModel/status.html:58 msgid "No AKs yet" msgstr "Bisher keine AKs" -#: AKModel/templates/admin/AKModel/status.html:59 +#: AKModel/templates/admin/AKModel/status.html:66 msgid "Slots" msgstr "Slots" -#: AKModel/templates/admin/AKModel/status.html:62 +#: AKModel/templates/admin/AKModel/status.html:69 msgid "Unscheduled Slots" msgstr "Ungeplante Slots" -#: AKModel/templates/admin/AKModel/status.html:76 +#: AKModel/templates/admin/AKModel/status.html:83 #: AKModel/templates/admin/ak_index.html:16 msgid "Scheduling" msgstr "Scheduling" -#: AKModel/templates/admin/AKModel/status.html:81 +#: AKModel/templates/admin/AKModel/status.html:88 msgid "AKs requiring special attention" msgstr "AKs, die besondere Aufmerksamkeit benötigen" -#: AKModel/templates/admin/AKModel/status.html:83 +#: AKModel/templates/admin/AKModel/status.html:90 msgid "Enter Interest" msgstr "Interesse erfassen" -#: AKModel/templates/admin/AKModel/status.html:86 +#: AKModel/templates/admin/AKModel/status.html:93 msgid "Manage ak tracks" msgstr "AK-Tracks verwalten" -#: AKModel/templates/admin/AKModel/status.html:88 +#: AKModel/templates/admin/AKModel/status.html:95 msgid "Export AKs as CSV" msgstr "AKs als CSV exportieren" -#: AKModel/templates/admin/AKModel/status.html:90 +#: AKModel/templates/admin/AKModel/status.html:97 msgid "Export AKs for Wiki" msgstr "AKs im Wiki-Format exportieren" -#: AKModel/templates/admin/AKModel/status.html:92 +#: AKModel/templates/admin/AKModel/status.html:99 AKModel/views.py:330 msgid "Export AK Slides" msgstr "AK-Folien exportieren" -#: AKModel/templates/admin/AKModel/status.html:94 -msgid "Export AK Slides (Presentation AKs only)" -msgstr "AK-Folien exportieren (Nur zu präsentierende AKs)" - -#: AKModel/templates/admin/AKModel/status.html:99 +#: AKModel/templates/admin/AKModel/status.html:104 msgid "No requirements yet" msgstr "Bisher keine Anforderungen" -#: AKModel/templates/admin/AKModel/status.html:112 +#: AKModel/templates/admin/AKModel/status.html:117 msgid "Show AKs for requirements" msgstr "Zu Anforderungen gehörige AKs anzeigen" -#: AKModel/templates/admin/AKModel/status.html:116 +#: AKModel/templates/admin/AKModel/status.html:121 msgid "Messages" msgstr "Nachrichten" -#: AKModel/templates/admin/AKModel/status.html:118 +#: AKModel/templates/admin/AKModel/status.html:123 msgid "Delete all messages" msgstr "Alle Nachrichten löschen" @@ -1012,27 +1065,27 @@ msgstr "Login" msgid "Register" msgstr "Registrieren" -#: AKModel/views.py:144 +#: AKModel/views.py:148 msgid "Event Status" msgstr "Eventstatus" -#: AKModel/views.py:157 +#: AKModel/views.py:161 msgid "Requirements for Event" msgstr "Anforderungen für das Event" -#: AKModel/views.py:171 +#: AKModel/views.py:175 msgid "AK CSV Export" msgstr "AK-CSV-Export" -#: AKModel/views.py:185 +#: AKModel/views.py:189 msgid "AK Wiki Export" msgstr "AK-Wiki-Export" -#: AKModel/views.py:193 AKModel/views.py:345 +#: AKModel/views.py:197 AKModel/views.py:346 msgid "Wishes" msgstr "Wünsche" -#: AKModel/views.py:215 +#: AKModel/views.py:218 msgid "Delete AK Orga Messages" msgstr "AK-Organachrichten löschen" @@ -1053,8 +1106,6 @@ msgid "Import categories & requirements" msgstr "Kategorien & Anforderungen kopieren" #: AKModel/views.py:246 -#, fuzzy -#| msgid "Active State" msgid "Activate?" msgstr "Aktivieren?" @@ -1068,22 +1119,82 @@ msgstr "'%(obj)s' kopiert" msgid "Could not copy '%(obj)s' (%(error)s)" msgstr "'%(obj)s' konnte nicht kopiert werden (%(error)s)" -#: AKModel/views.py:340 +#: AKModel/views.py:341 msgid "Symbols" msgstr "Symbole" -#: AKModel/views.py:341 +#: AKModel/views.py:342 msgid "Who?" msgstr "Wer?" -#: AKModel/views.py:342 +#: AKModel/views.py:343 msgid "Duration(s)" msgstr "Dauer(n)" -#: AKModel/views.py:343 +#: AKModel/views.py:344 msgid "Reso intention?" msgstr "Resolutionsabsicht?" -#: AKModel/views.py:344 +#: AKModel/views.py:345 msgid "Category (for Wishes)" msgstr "Kategorie (für Wünsche)" + +#: AKModel/views.py:433 +msgid "The following Constraint Violations will be marked as manually resolved" +msgstr "" +"Die folgenden Constraintverletzungen werden als manuell behoben markiert." + +#: AKModel/views.py:434 +msgid "Constraint Violations marked as resolved" +msgstr "Constraintverletzungen als manuell behoben markiert" + +#: AKModel/views.py:443 +msgid "The following Constraint Violations will be set to level 'violation'" +msgstr "" +"Die folgenden Constraintverletzungen werden auf das Level \"Violation\" " +"gesetzt." + +#: AKModel/views.py:444 +msgid "Constraint Violations set to level 'violation'" +msgstr "Constraintverletzungen auf Level \"Violation\" gesetzt" + +#: AKModel/views.py:453 +msgid "The following Constraint Violations will be set to level 'warning'" +msgstr "" +"Die folgenden Constraintverletzungen werden auf das Level 'warning' gesetzt." + +#: AKModel/views.py:454 +msgid "Constraint Violations set to level 'warning'" +msgstr "Constraintverletzungen auf Level \"Warning\" gesetzt" + +#: AKModel/views.py:463 +msgid "Interest of the following AKs will be set to not filled (-1):" +msgstr "Interesse an den folgenden AKs wird auf nicht ausgefüllt (-1) gesetzt:" + +#: AKModel/views.py:464 +msgid "Reset of interest in AKs successful." +msgstr "Interesse an AKs erfolgreich zurückgesetzt." + +#: AKModel/views.py:473 +msgid "Interest counter of the following AKs will be set to 0:" +msgstr "Interessensbekundungszähler der folgenden AKs wird auf 0 gesetzt:" + +#: AKModel/views.py:474 +msgid "AKs' interest counters set back to 0." +msgstr "Interessenszähler der AKs zurückgesetzt" + +#: AKModel/views.py:483 +msgid "Publish the plan(s) of:" +msgstr "Den Plan/die Pläne veröffentlichen von:" + +#: AKModel/views.py:484 +msgid "Plan published" +msgstr "Plan veröffentlicht" + +#: AKModel/views.py:493 +msgid "Unpublish the plan(s) of:" +msgstr "Den Plan/die Pläne verbergen von:" + +#: AKModel/views.py:494 +msgid "Plan unpublished" +msgstr "Plan verborgen" diff --git a/AKModel/models.py b/AKModel/models.py index dee3b93e38e4de618d094ff766855ffeda5dc7eb..a1896eac4763445dd95f81c11ce1b72b809ace6a 100644 --- a/AKModel/models.py +++ b/AKModel/models.py @@ -73,7 +73,7 @@ class Event(models.Model): event = Event.objects.order_by('start').filter(start__gt=datetime.now()).first() 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 @@ -97,7 +97,8 @@ class Event(models.Model): else: if filter(ak): ak_list.append(ak) - categories_with_aks.append((category, ak_list)) + if not hide_empty_categories or len(ak_list) > 0: + categories_with_aks.append((category, ak_list)) return categories_with_aks, ak_wishes else: for category in categories: @@ -105,7 +106,8 @@ class Event(models.Model): for ak in category.ak_set.all(): if filter(ak): ak_list.append(ak) - categories_with_aks.append((category, ak_list)) + if not hide_empty_categories or len(ak_list) > 0: + categories_with_aks.append((category, ak_list)) return categories_with_aks def get_unscheduled_wish_slots(self): diff --git a/AKModel/templates/admin/AKModel/status.html b/AKModel/templates/admin/AKModel/status.html index 362c72406804e4973d9600363511aac46226bbe4..d279efeafa139ae7f9c82ba7be404ea4a3474a2e 100644 --- a/AKModel/templates/admin/AKModel/status.html +++ b/AKModel/templates/admin/AKModel/status.html @@ -11,6 +11,13 @@ <h2><a href="{% url 'admin:AKModel_event_change' event.pk %}">{{event}}</a></h2> <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="col-md-8"> <h3 class="block-header">{% trans "Categories" %}</h3> @@ -89,9 +96,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..9bb8edc0cd97876d9714f51adfbd4667c4b4c565 100644 --- a/AKModel/views.py +++ b/AKModel/views.py @@ -1,19 +1,23 @@ +import os +import tempfile from abc import ABC, abstractmethod 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.db.models.functions import Now 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.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 -from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement + NewEventWizardImportForm, NewEventWizardActivateForm, AdminIntermediateForm, SlideExportForm, \ + AdminIntermediateActionForm +from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage, AKRequirement, \ + ConstraintViolation from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \ AKOwnerSerializer @@ -195,13 +199,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 +220,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 +326,172 @@ 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') + + +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) 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