From cf654570f92a2ec42098458568c68f2b9632be5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Tue, 16 Feb 2021 15:46:46 +0100
Subject: [PATCH] Introduce and use wizard for event creation and configuration

The wizard is used instead of the normal built-in creation workflow
It consists of multiple steps to specify the most fundamental settings (especially the timezone), further settings, copying AKCategories and AKRequirements from existing events and activating the event once the basic configuration is done
The steps after the creation (first two steps) can be skipped
---
 AKModel/admin.py                              |  32 ++-
 AKModel/forms.py                              |  67 +++++
 AKModel/locale/de_DE/LC_MESSAGES/django.po    | 250 +++++++++++++-----
 .../admin/AKModel/event_wizard/activate.html  |  36 +++
 .../event_wizard/created_prepare_import.html  |  52 ++++
 .../admin/AKModel/event_wizard/finish.html    |  24 ++
 .../admin/AKModel/event_wizard/import.html    |  28 ++
 .../admin/AKModel/event_wizard/settings.html  |  34 +++
 .../admin/AKModel/event_wizard/start.html     |  26 ++
 .../AKModel/event_wizard/wizard_steps.html    |  15 ++
 AKModel/views.py                              |  96 ++++++-
 locale/de_DE/LC_MESSAGES/django.po            |  14 +-
 12 files changed, 591 insertions(+), 83 deletions(-)
 create mode 100644 AKModel/forms.py
 create mode 100644 AKModel/templates/admin/AKModel/event_wizard/activate.html
 create mode 100644 AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html
 create mode 100644 AKModel/templates/admin/AKModel/event_wizard/finish.html
 create mode 100644 AKModel/templates/admin/AKModel/event_wizard/import.html
 create mode 100644 AKModel/templates/admin/AKModel/event_wizard/settings.html
 create mode 100644 AKModel/templates/admin/AKModel/event_wizard/start.html
 create mode 100644 AKModel/templates/admin/AKModel/event_wizard/wizard_steps.html

diff --git a/AKModel/admin.py b/AKModel/admin.py
index 48c1f878..81ab14a6 100644
--- a/AKModel/admin.py
+++ b/AKModel/admin.py
@@ -3,7 +3,7 @@ from django.contrib import admin
 from django.contrib.admin import SimpleListFilter
 from django.db.models import Count, F
 from django import forms
-from django.shortcuts import render
+from django.shortcuts import render, redirect
 from django.urls import path, reverse_lazy
 from django.utils import timezone
 from django.utils.html import format_html
@@ -16,7 +16,9 @@ from AKModel.availability.forms import AvailabilitiesFormMixin
 from AKModel.availability.models import Availability
 from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKTag, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \
     ConstraintViolation
-from AKModel.views import EventStatusView, AKCSVExportView, AKWikiExportView, AKMessageDeleteView
+from AKModel.views import EventStatusView, AKCSVExportView, AKWikiExportView, AKMessageDeleteView, \
+    NewEventWizardStartView, NewEventWizardSettingsView, NewEventWizardPrepareImportView, NewEventWizardFinishView, \
+    NewEventWizardImportView, NewEventWizardActivateView
 
 
 @admin.register(Event)
@@ -27,9 +29,29 @@ class EventAdmin(admin.ModelAdmin):
     list_editable = ['active']
     ordering = ['-start']
 
+    def add_view(self, request, form_url='', extra_context=None):
+        # Always use wizard to create new events
+        # (the built-in form wouldn't work anyways since the timezone cannot be specified before starting to fill the form)
+        return redirect("admin:new_event_wizard_start")
+
     def get_urls(self):
         urls = super().get_urls()
         custom_urls = [
+            path('add/wizard/start/', self.admin_site.admin_view(NewEventWizardStartView.as_view()),
+                 name="new_event_wizard_start"),
+            path('add/wizard/settings/', self.admin_site.admin_view(NewEventWizardSettingsView.as_view()),
+                 name="new_event_wizard_settings"),
+            path('add/wizard/created/<slug:event_slug>/', self.admin_site.admin_view(NewEventWizardPrepareImportView.as_view()),
+                 name="new_event_wizard_prepare_import"),
+            path('add/wizard/import/<slug:event_slug>/from/<slug:import_slug>/',
+                 self.admin_site.admin_view(NewEventWizardImportView.as_view()),
+                 name="new_event_wizard_import"),
+            path('add/wizard/activate/<slug:slug>/',
+                 self.admin_site.admin_view(NewEventWizardActivateView.as_view()),
+                 name="new_event_wizard_activate"),
+            path('add/wizard/finish/<slug:slug>/',
+                 self.admin_site.admin_view(NewEventWizardFinishView.as_view()),
+                 name="new_event_wizard_finish"),
             path('<slug:slug>/status/', self.admin_site.admin_view(EventStatusView.as_view()), name="event_status"),
             path('<slug:event_slug>/ak-csv-export/', self.admin_site.admin_view(AKCSVExportView.as_view()), name="ak_csv_export"),
             path('<slug:event_slug>/ak-wiki-export/', self.admin_site.admin_view(AKWikiExportView.as_view()), name="ak_wiki_export"),
@@ -45,11 +67,7 @@ class EventAdmin(admin.ModelAdmin):
 
     def get_form(self, request, obj=None, change=False, **kwargs):
         # Use timezone of event
-        if obj is not None and obj.timezone:
-            timezone.activate(obj.timezone)
-        # No timezone available? Use UTC
-        else:
-            timezone.activate("UTC")
+        timezone.activate(obj.timezone)
         return super().get_form(request, obj, change, **kwargs)
 
 
diff --git a/AKModel/forms.py b/AKModel/forms.py
new file mode 100644
index 00000000..3e39dcaa
--- /dev/null
+++ b/AKModel/forms.py
@@ -0,0 +1,67 @@
+from bootstrap_datepicker_plus import DateTimePickerInput
+from django import forms
+from django.forms.utils import ErrorList
+from django.utils.translation import ugettext_lazy as _
+
+from AKModel.models import Event, AKCategory, AKRequirement
+
+
+class NewEventWizardStartForm(forms.ModelForm):
+    class Meta:
+        model = Event
+        fields = ['name', 'slug', 'timezone']
+
+    is_init = forms.BooleanField(initial=True, widget=forms.HiddenInput)
+
+
+class NewEventWizardSettingsForm(forms.ModelForm):
+    class Meta:
+        model = Event
+        exclude = []
+        widgets = {
+            'name': forms.HiddenInput(),
+            'slug': forms.HiddenInput(),
+            'timezone': forms.HiddenInput(),
+            'active': forms.HiddenInput(),
+            'plan_hidden': forms.HiddenInput(),
+            'start': DateTimePickerInput(options={"format": "YYYY-MM-DD HH:mm"}),
+            'end': DateTimePickerInput(options={"format": "YYYY-MM-DD HH:mm"}),
+            'reso_deadline': DateTimePickerInput(options={"format": "YYYY-MM-DD HH:mm"}),
+        }
+
+
+class NewEventWizardPrepareImportForm(forms.Form):
+    import_event = forms.ModelChoiceField(
+        queryset=Event.objects.all(),
+        label=_("Copy ak requirements and ak categories of existing event"),
+        help_text=_("You can choose what to copy in the next step")
+    )
+
+class NewEventWizardImportForm(forms.Form):
+    import_categories = forms.ModelMultipleChoiceField(
+        queryset=AKCategory.objects.all(),
+        widget=forms.CheckboxSelectMultiple,
+        label=_("Copy ak categories"),
+        required=False,
+    )
+
+    import_requirements = forms.ModelMultipleChoiceField(
+        queryset=AKRequirement.objects.all(),
+        widget=forms.CheckboxSelectMultiple,
+        label=_("Copy ak requirements"),
+        required=False,
+    )
+
+    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=ErrorList,
+                 label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None,
+                 renderer=None):
+        super().__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, field_order,
+                         use_required_attribute, renderer)
+        self.fields["import_categories"].queryset = self.fields["import_categories"].queryset.filter(event=self.initial["import_event"])
+        self.fields["import_requirements"].queryset = self.fields["import_requirements"].queryset.filter(event=self.initial["import_event"])
+
+
+class NewEventWizardActivateForm(forms.ModelForm):
+    class Meta:
+        fields = ["active"]
+        model = Event
diff --git a/AKModel/locale/de_DE/LC_MESSAGES/django.po b/AKModel/locale/de_DE/LC_MESSAGES/django.po
index ebcaa384..5d16986b 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: 2020-11-03 20:41+0000\n"
+"POT-Creation-Date: 2021-02-16 14:42+0000\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,28 +11,35 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: AKModel/admin.py:41 AKModel/admin.py:42
+#: AKModel/admin.py:65 AKModel/admin.py:66
+#: 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
 #: AKModel/templates/admin/AKModel/status.html:7
 #: AKModel/templates/admin/ak_index.html:15
 msgid "Status"
 msgstr "Status"
 
-#: AKModel/admin.py:120
+#: AKModel/admin.py:140
 msgid "Wish"
 msgstr "AK-Wunsch"
 
-#: AKModel/admin.py:126
+#: AKModel/admin.py:146
 msgid "Is wish"
 msgstr "Ist ein Wunsch"
 
-#: AKModel/admin.py:127
+#: AKModel/admin.py:147
 msgid "Is not a wish"
 msgstr "Ist kein Wunsch"
 
-#: AKModel/admin.py:154
+#: AKModel/admin.py:174
 msgid "Export to wiki syntax"
 msgstr "In Wiki-Syntax exportieren"
 
+#: AKModel/admin.py:270
+msgid "AK Details"
+msgstr "AK-Details"
+
 #: AKModel/availability/forms.py:20 AKModel/availability/models.py:239
 msgid "Availability"
 msgstr "Verfügbarkeit"
@@ -60,13 +67,13 @@ msgstr "Bitte Verfügbarkeiten eintragen!"
 #: AKModel/availability/models.py:38 AKModel/models.py:47 AKModel/models.py:76
 #: AKModel/models.py:128 AKModel/models.py:147 AKModel/models.py:179
 #: AKModel/models.py:233 AKModel/models.py:292 AKModel/models.py:324
-#: AKModel/models.py:410
+#: AKModel/models.py:431
 msgid "Event"
 msgstr "Event"
 
 #: AKModel/availability/models.py:39 AKModel/models.py:77 AKModel/models.py:129
 #: AKModel/models.py:148 AKModel/models.py:180 AKModel/models.py:234
-#: AKModel/models.py:293 AKModel/models.py:325 AKModel/models.py:411
+#: AKModel/models.py:293 AKModel/models.py:325 AKModel/models.py:432
 msgid "Associated event"
 msgstr "Zugehöriges Event"
 
@@ -79,7 +86,7 @@ msgid "Person whose availability this is"
 msgstr "Person deren Verfügbarkeit hier abgebildet wird"
 
 #: AKModel/availability/models.py:56 AKModel/models.py:296
-#: AKModel/models.py:315 AKModel/models.py:419
+#: AKModel/models.py:315 AKModel/models.py:440
 msgid "Room"
 msgstr "Raum"
 
@@ -88,7 +95,7 @@ msgid "Room whose availability this is"
 msgstr "Raum dessen Verfügbarkeit hier abgebildet wird"
 
 #: AKModel/availability/models.py:65 AKModel/models.py:239
-#: AKModel/models.py:314 AKModel/models.py:366
+#: AKModel/models.py:314 AKModel/models.py:387
 msgid "AK"
 msgstr "AK"
 
@@ -97,7 +104,7 @@ msgid "AK whose availability this is"
 msgstr "Verfügbarkeiten"
 
 #: AKModel/availability/models.py:74 AKModel/models.py:132
-#: AKModel/models.py:425
+#: AKModel/models.py:446
 msgid "AK Category"
 msgstr "AK-Kategorie"
 
@@ -109,6 +116,23 @@ msgstr "AK-Kategorie, deren Verfügbarkeit hier abgebildet wird"
 msgid "Availabilities"
 msgstr "Verfügbarkeiten"
 
+#: AKModel/forms.py:36
+msgid "Copy ak requirements and ak categories of existing event"
+msgstr "AK-Anforderungen und AK-Kategorien eines existierenden Events kopieren"
+
+#: AKModel/forms.py:37
+msgid "You can choose what to copy in the next step"
+msgstr ""
+"Im nächsten Schritt kann ausgewählt werden, was genau kopiert werden soll"
+
+#: AKModel/forms.py:44
+msgid "Copy ak categories"
+msgstr "AK-Kategorien kopieren"
+
+#: AKModel/forms.py:51
+msgid "Copy ak requirements"
+msgstr "AK-Anforderungen kopieren"
+
 #: AKModel/models.py:16 AKModel/models.py:123 AKModel/models.py:144
 #: AKModel/models.py:163 AKModel/models.py:177 AKModel/models.py:195
 #: AKModel/models.py:285
@@ -145,7 +169,7 @@ msgstr "Zeitzone"
 msgid "Time Zone where this event takes place in"
 msgstr "Zeitzone in der das Event stattfindet"
 
-#: AKModel/models.py:25
+#: AKModel/models.py:25 AKModel/views.py:188
 msgid "Start"
 msgstr "Start"
 
@@ -261,7 +285,7 @@ msgstr "Internet Link"
 msgid "Link to Homepage"
 msgstr "Link zu Homepage oder Webseite"
 
-#: AKModel/models.py:80 AKModel/models.py:418
+#: AKModel/models.py:80 AKModel/models.py:439
 msgid "AK Owner"
 msgstr "AK-Leitung"
 
@@ -321,7 +345,7 @@ msgstr "AK-Tags"
 msgid "Name of the Requirement"
 msgstr "Name der Anforderung"
 
-#: AKModel/models.py:183 AKModel/models.py:422
+#: AKModel/models.py:183 AKModel/models.py:443
 msgid "AK Requirement"
 msgstr "AK-Anforderung"
 
@@ -458,7 +482,7 @@ msgstr "Interessenszähler"
 msgid "People who have indicated interest online"
 msgstr "Anzahl Personen, die online Interesse bekundet haben"
 
-#: AKModel/models.py:240 AKModel/models.py:413
+#: AKModel/models.py:240 AKModel/models.py:434
 #: AKModel/templates/admin/AKModel/status.html:49
 #: AKModel/templates/admin/AKModel/status.html:56
 msgid "AKs"
@@ -536,163 +560,168 @@ msgstr "Letzte Aktualisierung"
 msgid "AK Slot"
 msgstr "AK-Slot"
 
-#: AKModel/models.py:331 AKModel/models.py:415
+#: AKModel/models.py:331 AKModel/models.py:436
 msgid "AK Slots"
 msgstr "AK-Slot"
 
-#: AKModel/models.py:345
+#: AKModel/models.py:353 AKModel/models.py:362
 msgid "Not scheduled yet"
 msgstr "Noch nicht geplant"
 
-#: AKModel/models.py:367
+#: AKModel/models.py:388
 #, fuzzy
 #| msgid "Track the AK belongs to"
 msgid "AK this message belongs to"
 msgstr "Track zu dem der AK gehört"
 
-#: AKModel/models.py:368
+#: AKModel/models.py:389
 msgid "Message text"
 msgstr "Nachrichtentext"
 
-#: AKModel/models.py:369
+#: AKModel/models.py:390
 msgid "Message to the organizers. This is not publicly visible."
 msgstr ""
 "Nachricht an die Organisator*innen. Diese ist nicht öffentlich sichtbar."
 
-#: AKModel/models.py:373
+#: AKModel/models.py:394
 msgid "AK Orga Message"
 msgstr "AK-Organachricht"
 
-#: AKModel/models.py:374
+#: AKModel/models.py:395
 msgid "AK Orga Messages"
 msgstr "AK-Organachrichten"
 
-#: AKModel/models.py:383
+#: AKModel/models.py:404
 msgid "Constraint Violation"
 msgstr "Constraintverletzung"
 
-#: AKModel/models.py:384
+#: AKModel/models.py:405
 msgid "Constraint Violations"
 msgstr "Constraintverletzungen"
 
-#: AKModel/models.py:388
+#: AKModel/models.py:409
 msgid "Owner has two parallel slots"
 msgstr "Leitung hat zwei Slots parallel"
 
-#: AKModel/models.py:389
+#: AKModel/models.py:410
 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:390
+#: AKModel/models.py:411
 msgid "Room has two AK slots scheduled at the same time"
 msgstr "Raum hat AK Slots gleichzeitig"
 
-#: AKModel/models.py:391
+#: AKModel/models.py:412
 msgid "Room does not satisfy the requirement of the scheduled AK"
 msgstr "Room erfüllt die Anforderungen des platzierten AKs nicht"
 
-#: AKModel/models.py:392
+#: AKModel/models.py:413
 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"
+msgstr ""
+"AK Slot wurde wurde zur gleichen Zeit wie ein Konflikt des AKs platziert"
 
-#: AKModel/models.py:393
+#: AKModel/models.py:414
 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:395
-msgid "AK Slot for AK with intention to submit a resolution is scheduled after "
+#: AKModel/models.py:416
+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"
+msgstr ""
+"AK Slot eines AKs mit Resoabsicht wurde nach der Resodeadline platziert"
 
-#: AKModel/models.py:396
+#: AKModel/models.py:417
 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:397
+#: AKModel/models.py:418
 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:398
+#: AKModel/models.py:419
 msgid "AK Slot is scheduled in a room with less space than interest"
-msgstr "AK Slot wurde in einem Raum mit weniger Plätzen als am AK Interessierten platziert"
+msgstr ""
+"AK Slot wurde in einem Raum mit weniger Plätzen als am AK Interessierten "
+"platziert"
 
-#: AKModel/models.py:399
+#: AKModel/models.py:420
 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:402
+#: AKModel/models.py:423
 msgid "Warning"
 msgstr "Warnung"
 
-#: AKModel/models.py:403
+#: AKModel/models.py:424
 msgid "Violation"
 msgstr "Verletzung"
 
-#: AKModel/models.py:405
+#: AKModel/models.py:426
 msgid "Type"
 msgstr "Art"
 
-#: AKModel/models.py:406
+#: AKModel/models.py:427
 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:407
+#: AKModel/models.py:428
 msgid "Level"
 msgstr "Level"
 
-#: AKModel/models.py:408
+#: AKModel/models.py:429
 msgid "Severity level of the violation"
 msgstr "Schweregrad der Verletzung"
 
-#: AKModel/models.py:414
+#: AKModel/models.py:435
 msgid "AK(s) belonging to this constraint"
 msgstr "AK(s), die zu diesem Constraint gehören"
 
-#: AKModel/models.py:416
+#: AKModel/models.py:437
 msgid "AK Slot(s) belonging to this constraint"
 msgstr "AK Slot(s), die zu diesem Constraint gehören"
 
-#: AKModel/models.py:418
+#: AKModel/models.py:439
 msgid "AK Owner belonging to this constraint"
 msgstr "AK Leitung(en), die zu diesem Constraint gehören"
 
-#: AKModel/models.py:420
+#: AKModel/models.py:441
 msgid "Room belonging to this constraint"
 msgstr "Raum, der zu diesem Constraint gehört"
 
-#: AKModel/models.py:423
+#: AKModel/models.py:444
 msgid "AK Requirement belonging to this constraint"
 msgstr "AK Anforderung, die zu diesem Constraint gehört"
 
-#: AKModel/models.py:425
+#: AKModel/models.py:446
 msgid "AK Category belonging to this constraint"
 msgstr "AK Kategorie, di zu diesem Constraint gehört"
 
-#: AKModel/models.py:427
+#: AKModel/models.py:448
 msgid "Comment"
 msgstr "Kommentar"
 
-#: AKModel/models.py:427
+#: AKModel/models.py:448
 msgid "Comment or further details for this violation"
 msgstr "Kommentar oder weitere Details zu dieser Vereletzung"
 
-#: AKModel/models.py:430
+#: AKModel/models.py:451
 msgid "Timestamp"
 msgstr "Timestamp"
 
-#: AKModel/models.py:430
+#: AKModel/models.py:451
 msgid "Time of creation"
 msgstr "Zeitpunkt der ERstellung"
 
-#: AKModel/models.py:431
+#: AKModel/models.py:452
 msgid "Manually Resolved"
 msgstr "Manuell behoben"
 
-#: AKModel/models.py:432
+#: AKModel/models.py:453
 msgid "Mark this violation manually as resolved"
 msgstr "Markiere diese Verletzung manuell als behoben"
 
-#: AKModel/models.py:454
+#: AKModel/models.py:475
 msgid "Details"
 msgstr "Details"
 
@@ -717,6 +746,71 @@ msgstr ""
 msgid "Logout"
 msgstr "Ausloggen"
 
+#: AKModel/templates/admin/AKModel/event_wizard/activate.html:9
+#: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:9
+#: AKModel/templates/admin/AKModel/event_wizard/finish.html:9
+#: AKModel/templates/admin/AKModel/event_wizard/import.html:9
+#: AKModel/templates/admin/AKModel/event_wizard/settings.html:9
+#: AKModel/templates/admin/AKModel/event_wizard/start.html:8
+#: AKModel/templates/admin/AKModel/event_wizard/wizard_steps.html:3
+msgid "New event wizard"
+msgstr "Assistent zum Anlegen eines neuen Events"
+
+#: AKModel/templates/admin/AKModel/event_wizard/activate.html:18
+msgid "Successfully imported.<br><br>Do you want to activate your event now?"
+msgstr "Erfolgreich importiert.<br><br>Soll das Event jetzt aktiviert werden?"
+
+#: AKModel/templates/admin/AKModel/event_wizard/activate.html:27
+#: AKModel/views.py:193
+msgid "Finish"
+msgstr "Abschluss"
+
+#: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:16
+msgid "New event:"
+msgstr "Neues Event:"
+
+#: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:30
+msgid "Your event was created and can now be further configured."
+msgstr "Das Event wurde angelegt und kann nun weiter konfiguriert werden."
+
+#: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:39
+msgid "Skip Import"
+msgstr "Import überspringen"
+
+#: AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html:43
+#: AKModel/templates/admin/AKModel/event_wizard/import.html:20
+#: AKModel/templates/admin/AKModel/event_wizard/settings.html:22
+#: AKModel/templates/admin/AKModel/event_wizard/start.html:19
+msgid "Continue"
+msgstr "Fortfahren"
+
+#: AKModel/templates/admin/AKModel/event_wizard/finish.html:18
+msgid "Congratulations. Everything is set up!"
+msgstr "Herzlichen Glückwunsch. Alles ist eingerichtet!"
+
+#: AKModel/templates/admin/AKModel/event_wizard/import.html:24
+#: AKModel/templates/admin/AKModel/event_wizard/settings.html:29
+#: AKModel/templates/admin/AKModel/event_wizard/start.html:23
+#: AKModel/templates/admin/AKModel/message_delete.html:21
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: AKModel/templates/admin/AKModel/event_wizard/settings.html:26
+msgid "Back"
+msgstr "Zurück"
+
+#: AKModel/templates/admin/AKModel/event_wizard/start.html:13
+msgid ""
+"Add a new event. Please start by filling these basic properties. You can "
+"specify more settings later."
+msgstr ""
+"Neues Event anlegen. Bitte zunächst diese Grundeinstellungen ausfüllen, "
+"weitere Einstellungen können später gesetzt werden."
+
+#: AKModel/templates/admin/AKModel/event_wizard/wizard_steps.html:15
+msgid "Step"
+msgstr "Schritt"
+
 #: AKModel/templates/admin/AKModel/message_delete.html:7
 msgid "Delete Orga-Messages"
 msgstr "Organachrichten löschen"
@@ -739,10 +833,6 @@ msgstr ""
 msgid "Delete"
 msgstr "Löschen"
 
-#: AKModel/templates/admin/AKModel/message_delete.html:21
-msgid "Cancel"
-msgstr "Abbrechen"
-
 #: AKModel/templates/admin/AKModel/status.html:16
 msgid "Categories"
 msgstr "Kategorien"
@@ -810,22 +900,50 @@ msgstr "Alle Nachrichten löschen"
 msgid "Active Events"
 msgstr "Aktive Events"
 
-#: AKModel/views.py:130
+#: AKModel/views.py:131
 msgid "Event Status"
 msgstr "Eventstatus"
 
-#: AKModel/views.py:144
+#: AKModel/views.py:145
 msgid "AK CSV Export"
 msgstr "AK-CSV-Export"
 
-#: AKModel/views.py:158
+#: AKModel/views.py:159
 msgid "AK Wiki Export"
 msgstr "AK-Wiki-Export"
 
-#: AKModel/views.py:178
+#: AKModel/views.py:179
 msgid "AK Orga Messages successfully deleted"
 msgstr "AK-Organachrichten erfolgreich gelöscht"
 
+#: AKModel/views.py:189
+msgid "Settings"
+msgstr "Einstellungen"
+
+#: AKModel/views.py:190
+msgid "Event created, Prepare Import"
+msgstr "Event angelegt, Import vorbereiten"
+
+#: AKModel/views.py:191
+msgid "Import categories & requirements"
+msgstr "Kategorien & Anforderungen kopieren"
+
+#: AKModel/views.py:192
+#, fuzzy
+#| msgid "Active State"
+msgid "Activate?"
+msgstr "Aktivieren?"
+
+#: AKModel/views.py:250
+#, python-format
+msgid "Copied '%(obj)s'"
+msgstr "'%(obj)s' kopiert"
+
+#: AKModel/views.py:252
+#, python-format
+msgid "Could not copy '%(obj)s' (%(error)s)"
+msgstr "'%(obj)s' konnte nicht kopiert werden (%(error)s)"
+
 #~ msgid "Confirm"
 #~ msgstr "Bestätigen"
 
diff --git a/AKModel/templates/admin/AKModel/event_wizard/activate.html b/AKModel/templates/admin/AKModel/event_wizard/activate.html
new file mode 100644
index 00000000..eb460420
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/event_wizard/activate.html
@@ -0,0 +1,36 @@
+{% extends "admin/base_site.html" %}
+{% load tags_AKModel %}
+
+{% load i18n %}
+{% load bootstrap4 %}
+{% load fontawesome_5 %}
+{% load tz %}
+
+{% block title %}{% trans "New event wizard" %}: {{ wizard_step_text }}{% endblock %}
+
+{% block content %}
+    {% include "admin/AKModel/event_wizard/wizard_steps.html" %}
+
+    <div class="text-center btn-success disabled mt-3 mb-3" style="font-size: 8em;">
+        {% fa5_icon "copy" "fas" %}
+    </div>
+
+    <h5 class="mb-3">{% trans "Successfully imported.<br><br>Do you want to activate your event now?" %}</h5>
+
+    {{ form.media }}
+
+    <form method="post">{% csrf_token %}
+        {% bootstrap_form form %}
+
+        <div class="float-right">
+            <button type="submit" class="save btn btn-success" value="Submit">
+            {% fa5_icon "check" 'fas' %} {% trans "Finish" %}
+        </button>
+        </div>
+
+        <a href="{% url 'admin:event_status' event.slug %}" class="btn btn-info">
+            {% fa5_icon "info" 'fas' %} {% trans "Status" %}
+        </a>
+    </form>
+
+{% endblock %}
diff --git a/AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html b/AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html
new file mode 100644
index 00000000..e1fc7165
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/event_wizard/created_prepare_import.html
@@ -0,0 +1,52 @@
+{% extends "admin/base_site.html" %}
+{% load tags_AKModel %}
+
+{% load i18n %}
+{% load bootstrap4 %}
+{% load fontawesome_5 %}
+{% load tz %}
+
+{% block title %}{% trans "New event wizard" %}: {{ wizard_step_text }}{% endblock %}
+
+{% block content %}
+    {% include "admin/AKModel/event_wizard/wizard_steps.html" %}
+
+    {% timezone event.timezone %}
+        <div class="card border-success mt-3 mb-3" style="max-width: 100%;">
+          <div class="card-header">{% trans "New event:" %}</div>
+          <div class="card-body">
+            <h4 class="card-title">{{event}}</h4>
+            <p class="card-text">{{ event.start }} - {{ event.end }}</p>
+          </div>
+        </div>
+    {% endtimezone %}
+
+    <div class="text-center btn-success disabled mb-3" style="font-size: 8em;">
+        {% fa5_icon "calendar-plus" "fas" %}
+    </div>
+
+
+
+    <h5 class="mb-3">{% trans "Your event was created and can now be further configured." %}</h5>
+
+    {{ form.media }}
+
+    <form method="post">{% csrf_token %}
+        {% bootstrap_form form %}
+
+        <div class="float-right">
+            <a href="{% url 'admin:new_event_wizard_activate' event.slug %}" class="btn btn-info">
+                {% fa5_icon "forward" 'fas' %} {% trans "Skip Import" %}
+            </a>
+
+            <button type="submit" class="save btn btn-success" value="Submit">
+            {% fa5_icon "check" 'fas' %} {% trans "Continue" %}
+        </button>
+        </div>
+
+        <a href="{% url 'admin:event_status' event.slug %}" class="btn btn-info">
+            {% fa5_icon "info" 'fas' %} {% trans "Status" %}
+        </a>
+    </form>
+
+{% endblock %}
diff --git a/AKModel/templates/admin/AKModel/event_wizard/finish.html b/AKModel/templates/admin/AKModel/event_wizard/finish.html
new file mode 100644
index 00000000..16d3090b
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/event_wizard/finish.html
@@ -0,0 +1,24 @@
+{% extends "admin/base_site.html" %}
+{% load tags_AKModel %}
+
+{% load i18n %}
+{% load bootstrap4 %}
+{% load fontawesome_5 %}
+{% load tz %}
+
+{% block title %}{% trans "New event wizard" %}: {{ wizard_step_text }}{% endblock %}
+
+{% block content %}
+    {% include "admin/AKModel/event_wizard/wizard_steps.html" %}
+
+    <div class="text-center btn-success disabled mt-3 mb-3" style="font-size: 8em;">
+        {% fa5_icon "check-circle" "fas" %}
+    </div>
+
+    <h5>{% trans "Congratulations. Everything is set up!" %}</h5>
+
+    <a href="{% url 'admin:event_status' event.slug %}" class="btn btn-info float-right">
+            {% fa5_icon "info" 'fas' %}&nbsp;{% trans "Status" %}
+    </a>
+
+{% endblock %}
diff --git a/AKModel/templates/admin/AKModel/event_wizard/import.html b/AKModel/templates/admin/AKModel/event_wizard/import.html
new file mode 100644
index 00000000..d899a2d0
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/event_wizard/import.html
@@ -0,0 +1,28 @@
+{% extends "admin/base_site.html" %}
+{% load tags_AKModel %}
+
+{% load i18n %}
+{% load bootstrap4 %}
+{% load fontawesome_5 %}
+{% load tz %}
+
+{% block title %}{% trans "New event wizard" %}: {{ wizard_step_text }}{% endblock %}
+
+{% block content %}
+    {% include "admin/AKModel/event_wizard/wizard_steps.html" %}
+
+    {{ form.media }}
+
+    <form method="post">{% csrf_token %}
+        {% bootstrap_form form %}
+
+        <button type="submit" class="save btn btn-success float-right" value="Submit">
+            {% fa5_icon "check" 'fas' %} {% trans "Continue" %}
+        </button>
+
+        <a href="{% url 'admin:index' %}" class="btn btn-info">
+            {% fa5_icon "times" 'fas' %} {% trans "Cancel" %}
+        </a>
+    </form>
+
+{% endblock %}
diff --git a/AKModel/templates/admin/AKModel/event_wizard/settings.html b/AKModel/templates/admin/AKModel/event_wizard/settings.html
new file mode 100644
index 00000000..0b306d7b
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/event_wizard/settings.html
@@ -0,0 +1,34 @@
+{% extends "admin/base_site.html" %}
+{% load tags_AKModel %}
+
+{% load i18n %}
+{% load bootstrap4 %}
+{% load fontawesome_5 %}
+{% load tz %}
+
+{% block title %}{% trans "New event wizard" %}: {{ wizard_step_text }}{% endblock %}
+
+{% block content %}
+    {% include "admin/AKModel/event_wizard/wizard_steps.html" %}
+
+    {{ form.media }}
+
+    {% timezone timezone %}
+
+    <form method="post">{% csrf_token %}
+        {% bootstrap_form form %}
+
+        <button type="submit" class="save btn btn-success float-right" value="Submit">
+            {% fa5_icon "check" 'fas' %} {% trans "Continue" %}
+        </button>
+
+        <a href="{% url 'admin:new_event_wizard_start' %}" class="btn btn-info">
+            {% fa5_icon "chevron-left" 'fas' %} {% trans "Back" %}
+        </a>
+        <a href="{% url 'admin:index' %}" class="btn btn-warning">
+            {% fa5_icon "times" 'fas' %} {% trans "Cancel" %}
+        </a>
+    </form>
+
+    {% endtimezone %}
+{% endblock %}
diff --git a/AKModel/templates/admin/AKModel/event_wizard/start.html b/AKModel/templates/admin/AKModel/event_wizard/start.html
new file mode 100644
index 00000000..762cadb5
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/event_wizard/start.html
@@ -0,0 +1,26 @@
+{% extends "admin/base_site.html" %}
+{% load tags_AKModel %}
+
+{% load i18n %}
+{% load bootstrap4 %}
+{% load fontawesome_5 %}
+
+{% block title %}{% trans "New event wizard" %}: {{ wizard_step_text }}{% endblock %}
+
+{% block content %}
+    {% include "admin/AKModel/event_wizard/wizard_steps.html" %}
+
+    {%  trans "Add a new event. Please start by filling these basic properties. You can specify more settings later." %}
+
+    <form method="post" action="{% url 'admin:new_event_wizard_settings' %}">{% csrf_token %}
+        {% bootstrap_form form %}
+
+        <button type="submit" class="save btn btn-success float-right" value="Submit">
+            {% fa5_icon "check" 'fas' %} {% trans "Continue" %}
+        </button>
+
+        <a href="{% url 'admin:index' %}" class="btn btn-info">
+            {% fa5_icon "times" 'fas' %} {% trans "Cancel" %}
+        </a>
+    </form>
+{% endblock %}
diff --git a/AKModel/templates/admin/AKModel/event_wizard/wizard_steps.html b/AKModel/templates/admin/AKModel/event_wizard/wizard_steps.html
new file mode 100644
index 00000000..a006f3d3
--- /dev/null
+++ b/AKModel/templates/admin/AKModel/event_wizard/wizard_steps.html
@@ -0,0 +1,15 @@
+{% load i18n %}
+
+<h2>{% trans "New event wizard" %}</h2>
+
+<div>
+  <ul class="pagination pagination-sm">
+    {% for step in wizard_steps %}
+    <li class="page-item {% if forloop.counter == wizard_step %}active{% else %}disabled{% endif %}">
+      <a class="page-link" href="#">{{ step }}</a>
+    </li>
+    {% endfor %}
+  </ul>
+</div>
+
+<h3>{% trans "Step" %} {{ wizard_step }}: {{ wizard_step_text }}</h3>
diff --git a/AKModel/views.py b/AKModel/views.py
index d560e9f6..49269dad 100644
--- a/AKModel/views.py
+++ b/AKModel/views.py
@@ -1,12 +1,13 @@
 from django.contrib import admin, messages
 from django.http import HttpResponseRedirect
-from django.shortcuts import get_object_or_404
+from django.shortcuts import get_object_or_404, redirect
 from django.urls import reverse_lazy
 from django.utils.translation import gettext_lazy as _
-
-from django.views.generic import TemplateView, DetailView, ListView, DeleteView
+from django.views.generic import TemplateView, DetailView, ListView, DeleteView, CreateView, FormView, UpdateView
 from rest_framework import viewsets, permissions, mixins
 
+from AKModel.forms import NewEventWizardStartForm, NewEventWizardSettingsForm, NewEventWizardPrepareImportForm, \
+    NewEventWizardImportForm, NewEventWizardActivateForm
 from AKModel.models import Event, AK, AKSlot, Room, AKTrack, AKCategory, AKOwner, AKOrgaMessage
 from AKModel.serializers import AKSerializer, AKSlotSerializer, RoomSerializer, AKTrackSerializer, AKCategorySerializer, \
     AKOwnerSerializer
@@ -177,3 +178,92 @@ class AKMessageDeleteView(AdminViewMixin, DeleteView):
         self.get_orga_messages_for_event(self.get_object()).delete()
         messages.add_message(self.request, messages.SUCCESS, _("AK Orga Messages successfully deleted"))
         return HttpResponseRedirect(reverse_lazy('admin:event_status', kwargs={'slug': self.get_object().slug}))
+
+
+class WizardViewMixin:
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context["wizard_step"] = self.wizard_step
+        context["wizard_steps"] = [
+            _("Start"),
+            _("Settings"),
+            _("Event created, Prepare Import"),
+            _("Import categories & requirements"),
+            _("Activate?"),
+            _("Finish")
+        ]
+        context["wizard_step_text"] = context["wizard_steps"][self.wizard_step - 1]
+        context["wizard_steps_total"] = len(context["wizard_steps"])
+        return context
+
+
+class NewEventWizardStartView(AdminViewMixin, WizardViewMixin,  CreateView):
+    model = Event
+    form_class = NewEventWizardStartForm
+    template_name = "admin/AKModel/event_wizard/start.html"
+    wizard_step = 1
+
+
+class NewEventWizardSettingsView(AdminViewMixin, WizardViewMixin, CreateView):
+    model = Event
+    form_class = NewEventWizardSettingsForm
+    template_name = "admin/AKModel/event_wizard/settings.html"
+    wizard_step = 2
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context["timezone"] = context["form"].cleaned_data["timezone"]
+        return context
+
+    def get_success_url(self):
+        return reverse_lazy("admin:new_event_wizard_prepare_import", kwargs={"event_slug": self.object.slug})
+
+
+class NewEventWizardPrepareImportView(WizardViewMixin, EventSlugMixin, FormView):
+    form_class = NewEventWizardPrepareImportForm
+    template_name = "admin/AKModel/event_wizard/created_prepare_import.html"
+    wizard_step = 3
+
+    def form_valid(self, form):
+        # Selected a valid event to import from? Use this to go to next step of wizard
+        return redirect("admin:new_event_wizard_import", event_slug=self.event.slug, import_slug=form.cleaned_data["import_event"].slug)
+
+
+class NewEventWizardImportView(EventSlugMixin, WizardViewMixin, FormView):
+    form_class = NewEventWizardImportForm
+    template_name = "admin/AKModel/event_wizard/import.html"
+    wizard_step = 4
+
+    def get_initial(self):
+        initial = super().get_initial()
+        initial["import_event"] = Event.objects.get(slug=self.kwargs["import_slug"])
+        return initial
+
+    def form_valid(self, form):
+        for import_type in ["import_categories", "import_requirements"]:
+            for import_obj in form.cleaned_data.get(import_type):
+                # clone existing entry
+                try:
+                    import_obj.event = self.event
+                    import_obj.pk = None
+                    import_obj.save()
+                    messages.add_message(self.request, messages.SUCCESS, _("Copied '%(obj)s'" % {'obj': import_obj}))
+                except BaseException as e:
+                    messages.add_message(self.request, messages.ERROR, _("Could not copy '%(obj)s' (%(error)s)" %  {'obj': import_obj, "error": str(e)}))
+        return redirect("admin:new_event_wizard_activate", slug=self.event.slug)
+
+
+class NewEventWizardActivateView(WizardViewMixin, UpdateView):
+    model = Event
+    template_name = "admin/AKModel/event_wizard/activate.html"
+    form_class = NewEventWizardActivateForm
+    wizard_step = 5
+
+    def get_success_url(self):
+        return reverse_lazy("admin:new_event_wizard_finish", kwargs={"slug": self.object.slug})
+
+
+class NewEventWizardFinishView(WizardViewMixin, DetailView):
+    model = Event
+    template_name = "admin/AKModel/event_wizard/finish.html"
+    wizard_step = 6
diff --git a/locale/de_DE/LC_MESSAGES/django.po b/locale/de_DE/LC_MESSAGES/django.po
index 2f8b8562..25b6fc42 100644
--- a/locale/de_DE/LC_MESSAGES/django.po
+++ b/locale/de_DE/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-11-03 17:40+0000\n"
+"POT-Creation-Date: 2021-02-16 14:42+0000\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"
@@ -38,27 +38,27 @@ msgstr "Virtuelle Räume"
 msgid "Scheduling for"
 msgstr "Scheduling für"
 
-#: AKScheduling/templates/admin/AKScheduling/scheduling.html:94
+#: AKScheduling/templates/admin/AKScheduling/scheduling.html:87
 msgid "Day (Horizontal)"
 msgstr "Tag (horizontal)"
 
-#: AKScheduling/templates/admin/AKScheduling/scheduling.html:101
+#: AKScheduling/templates/admin/AKScheduling/scheduling.html:94
 msgid "Day (Vertical)"
 msgstr "Tag (vertikal)"
 
-#: AKScheduling/templates/admin/AKScheduling/scheduling.html:112
+#: AKScheduling/templates/admin/AKScheduling/scheduling.html:105
 msgid "Event (Horizontal)"
 msgstr "Event (horizontal)"
 
-#: AKScheduling/templates/admin/AKScheduling/scheduling.html:121
+#: AKScheduling/templates/admin/AKScheduling/scheduling.html:114
 msgid "Event (Vertical)"
 msgstr "Event (vertikal)"
 
-#: AKScheduling/templates/admin/AKScheduling/scheduling.html:148
+#: AKScheduling/templates/admin/AKScheduling/scheduling.html:141
 msgid "Room"
 msgstr "Raum"
 
-#: AKScheduling/templates/admin/AKScheduling/scheduling.html:204
+#: AKScheduling/templates/admin/AKScheduling/scheduling.html:197
 #: AKScheduling/templates/admin/AKScheduling/unscheduled.html:34
 msgid "Event Status"
 msgstr "Event-Status"
-- 
GitLab