From 19ff8e5a858d04bb9e4eec8e5edb56b1b98196bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@cs.tu-darmstadt.de>
Date: Thu, 24 Oct 2019 23:35:55 +0200
Subject: [PATCH] Improve submission and update forms for AKs as well as
 underlying model

Improve blank and unique constraints of model:
- allow blank starting times of slotes to store durations before scheduling and adapt string representation
- change unique constraint from name and shortname of AK to unique for each event only
Implement handling of tags for submission and update (auto-creation) by using a semicolon-separated text file instead of multiple choice field
Implement handling of durations for submission
Automatically generate short name
Use different forms for submission and update
Use special adapted template for AK updates instead of submission template
Fix EventSlugMixin for POST requests
---
 AKModel/locale/de_DE/LC_MESSAGES/django.po    | 194 ++++++++++--------
 .../migrations/0019_slot_start_optional.py    |  18 ++
 AKModel/migrations/0020_ak_unique.py          |  27 +++
 AKModel/models.py                             |  10 +-
 AKModel/views.py                              |  16 +-
 AKSubmission/forms.py                         |  62 +++++-
 .../locale/de_DE/LC_MESSAGES/django.po        |  35 +++-
 .../templates/AKSubmission/ak_edit.html       |  22 ++
 AKSubmission/views.py                         |  43 ++--
 9 files changed, 306 insertions(+), 121 deletions(-)
 create mode 100644 AKModel/migrations/0019_slot_start_optional.py
 create mode 100644 AKModel/migrations/0020_ak_unique.py
 create mode 100644 AKSubmission/templates/AKSubmission/ak_edit.html

diff --git a/AKModel/locale/de_DE/LC_MESSAGES/django.po b/AKModel/locale/de_DE/LC_MESSAGES/django.po
index 6c91a57b..7945c257 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: 2019-10-20 17:51+0000\n"
+"POT-Creation-Date: 2019-10-24 21:00+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,13 +11,13 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: availability.py:38 models.py:20 models.py:40 models.py:105 models.py:153
-#: models.py:184 models.py:209
+#: availability.py:38 models.py:23 models.py:45 models.py:139 models.py:188
+#: models.py:220 models.py:246
 msgid "Event"
 msgstr "Event"
 
-#: availability.py:39 models.py:41 models.py:106 models.py:154 models.py:185
-#: models.py:210
+#: availability.py:39 models.py:46 models.py:140 models.py:189 models.py:221
+#: models.py:247
 msgid "Associated event"
 msgstr "Zugehöriges Event"
 
@@ -29,7 +29,7 @@ msgstr "Person"
 msgid "Person whose availability this is"
 msgstr "Person deren Verfügbarkeit hier abgebildet wird"
 
-#: availability.py:56 models.py:188 models.py:203
+#: availability.py:56 models.py:224 models.py:239
 msgid "Room"
 msgstr "Raum"
 
@@ -37,7 +37,7 @@ msgstr "Raum"
 msgid "Room whose availability this is"
 msgstr "Raum dessen Verfügbarkeit hier abgebildet wird"
 
-#: availability.py:65 models.py:157 models.py:202
+#: availability.py:65 models.py:192 models.py:238
 msgid "AK"
 msgstr "AK"
 
@@ -47,7 +47,7 @@ msgstr "AK"
 msgid "AK whose availability this is"
 msgstr "Verfügbarkeiten"
 
-#: availability.py:74 models.py:63
+#: availability.py:74 models.py:97
 msgid "AK Category"
 msgstr "AK Kategorie"
 
@@ -63,334 +63,346 @@ msgstr "Verfügbarkeit"
 msgid "Availabilities"
 msgstr "Verfügbarkeiten"
 
-#: models.py:9 models.py:58 models.py:74 models.py:89 models.py:103
-#: models.py:120 models.py:177
+#: models.py:12 models.py:92 models.py:108 models.py:123 models.py:137
+#: models.py:154 models.py:213
 msgid "Name"
 msgstr "Name"
 
-#: models.py:10
+#: models.py:13
 msgid "Name or iteration of the event"
 msgstr "Name oder Iteration des Events"
 
-#: models.py:11
+#: models.py:14
 #, fuzzy
 #| msgid "Short Name"
 msgid "Short Form"
 msgstr "Kurzer Name"
 
-#: models.py:12
+#: models.py:15
 msgid "Short name of letters/numbers/dots/dashes/underscores used in URLs."
 msgstr ""
 
-#: models.py:13
+#: models.py:16
 msgid "Start"
 msgstr "Start"
 
-#: models.py:13
+#: models.py:16
 msgid "Time the event begins"
 msgstr "Zeit zu der das Event beginnt"
 
-#: models.py:14
+#: models.py:17
 msgid "End"
 msgstr "Ende"
 
-#: models.py:14
+#: models.py:17
 msgid "Time the event ends"
 msgstr "Zeit zu der das Event endet"
 
-#: models.py:15
+#: models.py:18
 msgid "Place"
 msgstr "Ort"
 
-#: models.py:16
+#: models.py:19
 msgid "City etc. the event takes place in"
 msgstr "Stadt o.ä. in der das Event stattfindet"
 
-#: models.py:17
+#: models.py:20
 msgid "Active State"
 msgstr "Aktiver Status"
 
-#: models.py:17
+#: models.py:20
 msgid "Marks currently active events"
 msgstr "Markiert aktuell aktive Events"
 
-#: models.py:21
+#: models.py:24
 msgid "Events"
 msgstr "Events"
 
-#: models.py:35
+#: models.py:38
 msgid "Nickname"
 msgstr "Spitzname"
 
-#: models.py:35
+#: models.py:38
 msgid "Name to identify an AK owner by"
 msgstr "Name durch den eine AK Leitung identifiziert wird"
 
-#: models.py:36
+#: models.py:39
+msgid "Slug"
+msgstr "Slug"
+
+#: models.py:40
+msgid "Slug for URL generation"
+msgstr "Slug für URL-Generierung"
+
+#: models.py:41
 msgid "E-Mail Address"
 msgstr "E-Mail Adresse"
 
-#: models.py:36
+#: models.py:41
 msgid "Contact mail"
 msgstr "Kontakt E-Mail"
 
-#: models.py:37
+#: models.py:42
 msgid "Institution"
 msgstr "Instutution"
 
-#: models.py:37
+#: models.py:42
 msgid "Uni etc."
 msgstr "Universität o.ä."
 
-#: models.py:38 models.py:129
+#: models.py:43 models.py:163
 msgid "Web Link"
 msgstr "Internet Link"
 
-#: models.py:38
+#: models.py:43
 msgid "Link to Homepage"
 msgstr "Link zu Homepage oder Webseite"
 
-#: models.py:44
+#: models.py:49
 msgid "AK Owner"
 msgstr "AK Leitung"
 
-#: models.py:45
+#: models.py:50
 msgid "AK Owners"
 msgstr "AK Leitungen"
 
-#: models.py:58
+#: models.py:92
 msgid "Name of the AK Category"
 msgstr "Name des AK Kategorie"
 
-#: models.py:59 models.py:75
+#: models.py:93 models.py:109
 msgid "Color"
 msgstr "Farbe"
 
-#: models.py:59 models.py:75
+#: models.py:93 models.py:109
 msgid "Color for displaying"
 msgstr "Farbe für die Anzeige"
 
-#: models.py:60 models.py:123
+#: models.py:94 models.py:157
 msgid "Description"
 msgstr "Beschreibung"
 
-#: models.py:60
+#: models.py:94
 msgid "Short description of this AK Category"
 msgstr "Beschreibung der AK-Kategorie"
 
-#: models.py:64
+#: models.py:98
 msgid "AK Categories"
 msgstr "AK Kategorien"
 
-#: models.py:74
+#: models.py:108
 msgid "Name of the AK Track"
 msgstr "Name des AK Tracks"
 
-#: models.py:78
+#: models.py:112
 msgid "AK Track"
 msgstr "AK Track"
 
-#: models.py:79
+#: models.py:113
 msgid "AK Tracks"
 msgstr "AK Tracks"
 
-#: models.py:89
+#: models.py:123
 msgid "Name of the AK Tag"
 msgstr "Name das AK Tags"
 
-#: models.py:92
+#: models.py:126
 msgid "AK Tag"
 msgstr "AK Tag"
 
-#: models.py:93
+#: models.py:127
 msgid "AK Tags"
 msgstr "AK Tags"
 
-#: models.py:103
+#: models.py:137
 msgid "Name of the Requirement"
 msgstr "Name der Anforderung"
 
-#: models.py:109
+#: models.py:143
 msgid "AK Requirement"
 msgstr "AK Anforderung"
 
-#: models.py:110
+#: models.py:144
 msgid "AK Requirements"
 msgstr "AK Anforderungen"
 
-#: models.py:120
+#: models.py:154
 msgid "Name of the AK"
 msgstr "Name des AKs"
 
-#: models.py:121
+#: models.py:155
 msgid "Short Name"
 msgstr "Kurzer Name"
 
-#: models.py:122
+#: models.py:156
 msgid "Name displayed in the schedule"
 msgstr "Name zur Anzeige im AK Plan"
 
-#: models.py:123
+#: models.py:157
 msgid "Description of the AK"
 msgstr "Beschreibung des AKs"
 
-#: models.py:125
+#: models.py:159
 msgid "Owners"
 msgstr "Leitungen"
 
-#: models.py:126
+#: models.py:160
 msgid "Those organizing the AK"
 msgstr "Menschen, die den AK organisieren und halten"
 
-#: models.py:129
+#: models.py:163
 msgid "Link to wiki page"
 msgstr "Link zur Wiki Seite"
 
-#: models.py:131
+#: models.py:165
 msgid "Category"
 msgstr "Kategorie"
 
-#: models.py:132
+#: models.py:166
 msgid "Category of the AK"
 msgstr "Kategorie des AKs"
 
-#: models.py:133
+#: models.py:167
 msgid "Tags"
 msgstr "Tags"
 
-#: models.py:133
+#: models.py:167
 msgid "Tags provided by owners"
 msgstr "Tags, die durch die AK Leitung vergeben wurden"
 
-#: models.py:134
+#: models.py:168
 msgid "Track"
 msgstr "Track"
 
-#: models.py:135
+#: models.py:169
 msgid "Track the AK belongs to"
 msgstr "Track zu dem der AK gehört"
 
-#: models.py:137
+#: models.py:171
 msgid "Resolution Intention"
 msgstr "Resolutionsabsicht"
 
-#: models.py:138
+#: models.py:172
 msgid "Intends to submit a resolution"
 msgstr "Beabsichtigt eine Resolution einzureichen"
 
-#: models.py:139
+#: models.py:173
 msgid "Present this AK"
 msgstr "AK Präsentieren"
 
-#: models.py:139
+#: models.py:174
 msgid "Present results of this AK"
 msgstr "Die Ergebnisse dieses AKs vorstellen"
 
-#: models.py:141
+#: models.py:176
 msgid "Requirements"
 msgstr "Anforderungen"
 
-#: models.py:142
+#: models.py:177
 msgid "AK's Requirements"
 msgstr "Anforderungen des AKs"
 
-#: models.py:144
+#: models.py:179
 msgid "Conflicting AKs"
 msgstr "AK Konflikte"
 
-#: models.py:145
+#: models.py:180
 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"
 
-#: models.py:146
+#: models.py:181
 msgid "Prerequisite AKs"
 msgstr "Vorausgesetzte AKs"
 
-#: models.py:147
+#: models.py:182
 msgid "AKs that should precede this AK in the schedule"
 msgstr "AKS die im AK Plan vor diesem AK stattfinden müssen"
 
-#: models.py:149
+#: models.py:184
 msgid "Internal Notes"
 msgstr "Interne Notizen"
 
-#: models.py:149
+#: models.py:184
 msgid "Notes to organizers"
 msgstr "Notizen an die Organisator*innen"
 
-#: models.py:151
+#: models.py:186
 msgid "Interest"
 msgstr "Interesse"
 
-#: models.py:151
+#: models.py:186
 msgid "Expected number of people"
 msgstr "Erwartete Personenzahl"
 
-#: models.py:158
+#: models.py:193
 msgid "AKs"
 msgstr "AKs"
 
-#: models.py:177
+#: models.py:213
 msgid "Name or number of the room"
 msgstr "Name oder Nummer des Raums"
 
-#: models.py:178
+#: models.py:214
 msgid "Building"
 msgstr "Gebäude"
 
-#: models.py:179
+#: models.py:215
 msgid "Name or number of the building"
 msgstr "Name oder Nummer des Gebäudes"
 
-#: models.py:180
+#: models.py:216
 msgid "Capacity"
 msgstr "Kapazität"
 
-#: models.py:180
+#: models.py:216
 msgid "Maximum number of people"
 msgstr "Maximale Personenzahl"
 
-#: models.py:181
+#: models.py:217
 msgid "Properties"
 msgstr "Eigenschaften"
 
-#: models.py:182
+#: models.py:218
 msgid "AK requirements fulfilled by the room"
 msgstr "AK Anforderungen, die dieser Raum erfüllt"
 
-#: models.py:189
+#: models.py:225
 msgid "Rooms"
 msgstr "Räume"
 
-#: models.py:202
+#: models.py:238
 msgid "AK being mapped"
 msgstr "AK, der zugeordnet wird"
 
-#: models.py:204
+#: models.py:240
 msgid "Room the AK will take place in"
 msgstr "Raum in dem der AK stattfindet"
 
-#: models.py:205
+#: models.py:241
 msgid "Slot Begin"
 msgstr "Beginn des Slots"
 
-#: models.py:205
+#: models.py:241
 msgid "Time and date the slot begins"
 msgstr "Zeit und Datum zu der der AK beginnt"
 
-#: models.py:206
+#: models.py:243
 msgid "Duration"
 msgstr "Dauer"
 
-#: models.py:207
+#: models.py:244
 msgid "Length in hours"
 msgstr "Länge in Stunden"
 
-#: models.py:213
+#: models.py:250
 msgid "AK Slot"
 msgstr "AK Slot"
 
-#: models.py:214
+#: models.py:251
 msgid "AK Slots"
 msgstr "AK Slot"
+
+#: models.py:265
+msgid "Not scheduled yet"
+msgstr "Noch nicht geplant"
diff --git a/AKModel/migrations/0019_slot_start_optional.py b/AKModel/migrations/0019_slot_start_optional.py
new file mode 100644
index 00000000..0d04806f
--- /dev/null
+++ b/AKModel/migrations/0019_slot_start_optional.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.6 on 2019-10-24 15:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('AKModel', '0018_merge_20191023_2227'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='akslot',
+            name='start',
+            field=models.DateTimeField(blank=True, help_text='Time and date the slot begins', null=True, verbose_name='Slot Begin'),
+        ),
+    ]
diff --git a/AKModel/migrations/0020_ak_unique.py b/AKModel/migrations/0020_ak_unique.py
new file mode 100644
index 00000000..ee7fbb09
--- /dev/null
+++ b/AKModel/migrations/0020_ak_unique.py
@@ -0,0 +1,27 @@
+# Generated by Django 2.2.6 on 2019-10-24 19:51
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('AKModel', '0019_slot_start_optional'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='ak',
+            name='name',
+            field=models.CharField(help_text='Name of the AK', max_length=256, verbose_name='Name'),
+        ),
+        migrations.AlterField(
+            model_name='ak',
+            name='short_name',
+            field=models.CharField(blank=True, help_text='Name displayed in the schedule', max_length=64, verbose_name='Short Name'),
+        ),
+        migrations.AlterUniqueTogether(
+            name='ak',
+            unique_together={('short_name', 'event'), ('name', 'event')},
+        ),
+    ]
diff --git a/AKModel/models.py b/AKModel/models.py
index 81cb9ca4..690a9411 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -151,8 +151,8 @@ class AKRequirement(models.Model):
 class AK(models.Model):
     """ An AK is a slot-based activity to be scheduled during an event.
     """
-    name = models.CharField(max_length=256, unique=True, verbose_name=_('Name'), help_text=_('Name of the AK'))
-    short_name = models.CharField(max_length=64, unique=True, blank=True, verbose_name=_('Short Name'),
+    name = models.CharField(max_length=256, verbose_name=_('Name'), help_text=_('Name of the AK'))
+    short_name = models.CharField(max_length=64, blank=True, verbose_name=_('Short Name'),
                                   help_text=_('Name displayed in the schedule'))
     description = models.TextField(blank=True, verbose_name=_('Description'), help_text=_('Description of the AK'))
 
@@ -191,6 +191,7 @@ class AK(models.Model):
     class Meta:
         verbose_name = _('AK')
         verbose_name_plural = _('AKs')
+        unique_together = [('name', 'event'), ('short_name', 'event')]
 
     def __str__(self):
         if self.short_name:
@@ -237,7 +238,8 @@ class AKSlot(models.Model):
     ak = models.ForeignKey(to=AK, on_delete=models.CASCADE, verbose_name=_('AK'), help_text=_('AK being mapped'))
     room = models.ForeignKey(to=Room, blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('Room'),
                              help_text=_('Room the AK will take place in'))
-    start = models.DateTimeField(verbose_name=_('Slot Begin'), help_text=_('Time and date the slot begins'))
+    start = models.DateTimeField(verbose_name=_('Slot Begin'), help_text=_('Time and date the slot begins'),
+                                 blank=True, null=True)
     duration = models.DecimalField(max_digits=4, decimal_places=2, default=2, verbose_name=_('Duration'),
                                    help_text=_('Length in hours'))
 
@@ -259,4 +261,6 @@ class AKSlot(models.Model):
         """
         Display start time of slot in format weekday + time, e.g. "Fri 14:00"
         """
+        if self.start is None:
+            return _("Not scheduled yet")
         return self.start.strftime('%a %H:%M')
diff --git a/AKModel/views.py b/AKModel/views.py
index 84864063..1ef24635 100644
--- a/AKModel/views.py
+++ b/AKModel/views.py
@@ -1,4 +1,4 @@
-from django.http import Http404
+from django.shortcuts import get_object_or_404
 
 from AKModel.models import Event
 
@@ -9,14 +9,18 @@ class EventSlugMixin:
     """
     event = None
 
-    def get(self, request, *args, **kwargs):
+    def _load_event(self):
         # Find event based on event slug
-        try:
-            self.event = Event.get_by_slug(self.kwargs.get("event_slug", None))
-        except Event.DoesNotExist:
-            raise Http404
+        self.event = get_object_or_404(Event, slug=self.kwargs.get("event_slug", None))
+
+    def get(self, request, *args, **kwargs):
+        self._load_event()
         return super().get(request, *args, **kwargs)
 
+    def post(self, request, *args, **kwargs):
+        self._load_event()
+        return super().post(request, *args, **kwargs)
+
     def get_context_data(self, *, object_list=None, **kwargs):
         context = super().get_context_data(object_list=object_list, **kwargs)
         # Add event to context (to make it accessible in templates)
diff --git a/AKSubmission/forms.py b/AKSubmission/forms.py
index 414e4539..2d145419 100644
--- a/AKSubmission/forms.py
+++ b/AKSubmission/forms.py
@@ -2,6 +2,8 @@ from django import forms
 
 from AKModel.models import AK, AKOwner
 
+from django.utils.translation import ugettext_lazy as _
+
 
 class AKForm(forms.ModelForm):
     class Meta:
@@ -12,17 +14,18 @@ class AKForm(forms.ModelForm):
                   'owners',
                   'description',
                   'category',
-                  'tags',
                   'reso',
                   'present',
                   'requirements',
                   'conflicts',
                   'prerequisites',
                   'notes',
+                  'event'
                   ]
 
         widgets = {
             'requirements': forms.CheckboxSelectMultiple,
+            'event': forms.HiddenInput,
         }
 
     def __init__(self, *args, **kwargs):
@@ -32,8 +35,63 @@ class AKForm(forms.ModelForm):
         self.fields["conflicts"].widget.attrs = {'class': 'chosen-select'}
         self.fields["prerequisites"].widget.attrs = {'class': 'chosen-select'}
 
+        help_tags_addition = _('Separate multiple tags with semicolon')
+
+        # Add text fields for tags
+        self.fields["tags_raw"] = forms.CharField(
+            required=False,
+            label=AK.tags.field.verbose_name,
+            help_text=f"{AK.tags.field.help_text} ({help_tags_addition})")
+
+    @staticmethod
+    def _clean_duration(duration):
+        # Handle different duration formats (h:mm and decimal comma instead of point)
+        if ":" in duration:
+            h, m = duration.split(":")
+            duration = int(h) + int(m) / 60
+        if "," in str(duration):
+            duration = float(duration.replace(",", "."))
+        return duration
+
+    def clean(self):
+        cleaned_data = super().clean()
+
+        # Generate short name if not given
+        short_name = self.cleaned_data["short_name"]
+        if len(short_name) == 0:
+            cleaned_data["short_name"] = ''.join(x for x in self.cleaned_data["name"].title() if x.isalnum())
+
+        # Get tag names from raw tags
+        cleaned_data["tag_names"] = [name.strip().lower() for name in cleaned_data["tags_raw"].split(";")]
+
+        # Get durations from raw durations field
+        if "durations" in cleaned_data:
+            cleaned_data["durations"] = [self._clean_duration(d) for d in self.cleaned_data["durations"].split()]
+        return cleaned_data
+
+
+class AKSubmissionForm(AKForm):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        # Add field for durations
+        self.fields["durations"] = forms.CharField(
+            widget=forms.Textarea,
+            label=_("Duration(s)"),
+            help_text=_("Enter at least one planned duration (in hours). If your AK should have multiple slots, use multiple lines")
+        )
+
+
+class AKEditForm(AKForm):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        # Add existing tags to tag raw field
+        self.fields["tags_raw"].initial = "; ".join(str(tag) for tag in self.instance.tags.all())
+
 
-class AKWishForm(AKForm):
+class AKWishForm(AKSubmissionForm):
     class Meta(AKForm.Meta):
         exclude = ['owners']
 
diff --git a/AKSubmission/locale/de_DE/LC_MESSAGES/django.po b/AKSubmission/locale/de_DE/LC_MESSAGES/django.po
index e6500ad7..25f72410 100644
--- a/AKSubmission/locale/de_DE/LC_MESSAGES/django.po
+++ b/AKSubmission/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: 2019-10-24 14:18+0000\n"
+"POT-Creation-Date: 2019-10-24 21:34+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"
@@ -17,10 +17,27 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
+#: forms.py:38
+msgid "Separate multiple tags with semicolon"
+msgstr "Mehrere Tags mit Semikolon trennen"
+
+#: forms.py:80
+msgid "Duration(s)"
+msgstr "Dauer(n)"
+
+#: forms.py:81
+msgid ""
+"Enter at least one planned duration (in hours). If your AK should have "
+"multiple slots, use multiple lines"
+msgstr ""
+"Mindestens eine geplante Dauer (in Stunden) angeben. Wenn der AK mehrere "
+"Slots haben soll, mehrere Zeilen verwenden"
+
 #: templates/AKSubmission/ak_detail.html:8
 #: templates/AKSubmission/ak_detail.html:13
 #: templates/AKSubmission/ak_detail.html:18
-#: templates/AKSubmission/ak_list.html:8 templates/AKSubmission/ak_list.html:13
+#: templates/AKSubmission/ak_edit.html:14 templates/AKSubmission/ak_list.html:8
+#: templates/AKSubmission/ak_list.html:13
 #: templates/AKSubmission/ak_list.html:18
 #: templates/AKSubmission/akowner_create_update.html:13
 #: templates/AKSubmission/submission_overview.html:6
@@ -72,6 +89,14 @@ msgstr "Dauer"
 msgid "Room"
 msgstr "Raum"
 
+#: templates/AKSubmission/ak_edit.html:8 templates/AKSubmission/ak_edit.html:21
+msgid "Edit AK"
+msgstr "AK bearbeiten"
+
+#: templates/AKSubmission/ak_edit.html:16
+msgid "Edit"
+msgstr "Bearbeiten"
+
 #: templates/AKSubmission/ak_list.html:14
 msgid "AKs"
 msgstr "AKs"
@@ -163,14 +188,14 @@ msgstr ""
 msgid "AK successfully created"
 msgstr "AK erfolgreich angelegt"
 
-#: views.py:149
+#: views.py:151
 msgid "AK successfully updated"
 msgstr "AK erfolgreich bearbeitet"
 
-#: views.py:193
+#: views.py:208
 msgid "Person Info successfully updated"
 msgstr "Personen-Info erfolgreich bearbeitet"
 
-#: views.py:205
+#: views.py:220
 msgid "No user selected"
 msgstr "Keine Person ausgewählt"
diff --git a/AKSubmission/templates/AKSubmission/ak_edit.html b/AKSubmission/templates/AKSubmission/ak_edit.html
new file mode 100644
index 00000000..45b1f9d3
--- /dev/null
+++ b/AKSubmission/templates/AKSubmission/ak_edit.html
@@ -0,0 +1,22 @@
+{% extends 'AKSubmission/submit_new.html' %}
+
+{% load i18n %}
+{% load bootstrap4 %}
+{% load fontawesome %}
+{% load staticfiles %}
+
+{% block title %}{{ event.slug }} - {% trans "Edit AK" %}{% endblock %}
+
+{% block breadcrumbs %}
+    <li class="breadcrumb-item"><a href="#">AKPlanning</a></li>
+    <li class="breadcrumb-item"><a href="#">{{ event.slug }}</a></li>
+    <li class="breadcrumb-item"><a
+            href="{% url 'submit:submission_overview' event_slug=event.slug %}">{% trans "AK Submission" %}</a></li>
+    <li class="breadcrumb-item"><a href="#">{{ ak.short_name }}</a></li>
+    <li class="breadcrumb-item active">{% trans "Edit" %}</li>
+{% endblock %}
+
+
+{% block headline %}
+    <h2>{% trans 'Edit AK' %}</h2>
+{% endblock %}
diff --git a/AKSubmission/views.py b/AKSubmission/views.py
index 873cc975..b9011f86 100644
--- a/AKSubmission/views.py
+++ b/AKSubmission/views.py
@@ -6,12 +6,12 @@ from django.utils.translation import gettext_lazy as _
 from django.views import View
 from django.views.generic import ListView, DetailView, CreateView, UpdateView
 
-from AKModel.models import AK, AKCategory, AKTag, AKOwner
+from AKModel.models import AK, AKCategory, AKTag, AKOwner, AKSlot
 from AKModel.models import Event
 from AKModel.views import EventSlugMixin
 from AKModel.views import FilterByEventSlugMixin
 
-from AKSubmission.forms import AKForm, AKWishForm, AKOwnerForm
+from AKSubmission.forms import AKForm, AKWishForm, AKOwnerForm, AKEditForm, AKSubmissionForm
 
 from django.conf import settings
 
@@ -99,34 +99,36 @@ class AKListByTagView(AKListView):
 class AKAndAKWishSubmissionView(EventSlugMixin, CreateView):
     model = AK
     template_name = 'AKSubmission/submit_new.html'
-    form_class = AKForm
+    form_class = AKSubmissionForm
 
     def get_success_url(self):
         messages.add_message(self.request, messages.SUCCESS, _("AK successfully created"))
         return reverse_lazy('submit:ak_detail', kwargs={'event_slug': self.kwargs['event_slug'], 'pk': self.object.pk})
 
     def form_valid(self, form):
-        instance = form.save(commit=False)
-
-        # Set event
-        instance.event = Event.get_by_slug(self.kwargs["event_slug"])
-
-        # Generate short name if not given
-        # TODO
+        super_form_valid = super().form_valid(form)
 
         # Generate wiki link
         # TODO
 
+        # Set tags (and generate them if necessary)
+        for tag_name in form.cleaned_data["tag_names"]:
+            tag, _ = AKTag.objects.get_or_create(name=tag_name)
+            self.object.tags.add(tag)
+
         # Generate slot(s)
-        # TODO
+        for duration in form.cleaned_data["durations"]:
+            new_slot = AKSlot(ak=self.object, duration=duration, event=self.object.event)
+            new_slot.save()
 
-        return super().form_valid(form)
+        return super_form_valid
 
 
 class AKSubmissionView(AKAndAKWishSubmissionView):
     def get_initial(self):
         initials = super(AKAndAKWishSubmissionView, self).get_initial()
         initials['owners'] = [AKOwner.get_by_slug(self.kwargs['owner_slug'])]
+        initials['event'] = self.event
         return initials
 
     def get_context_data(self, *, object_list=None, **kwargs):
@@ -142,13 +144,26 @@ class AKWishSubmissionView(AKAndAKWishSubmissionView):
 
 class AKEditView(EventSlugMixin, UpdateView):
     model = AK
-    template_name = 'AKSubmission/submit_new.html'
-    form_class = AKForm
+    template_name = 'AKSubmission/ak_edit.html'
+    form_class = AKEditForm
 
     def get_success_url(self):
         messages.add_message(self.request, messages.SUCCESS, _("AK successfully updated"))
         return reverse_lazy('submit:ak_detail', kwargs={'event_slug': self.kwargs['event_slug'], 'pk': self.object.pk})
 
+    def form_valid(self, form):
+        super_form_valid = super().form_valid(form)
+
+        # Detach existing tags
+        self.object.tags.clear()
+
+        # Set tags (and generate them if necessary)
+        for tag_name in form.cleaned_data["tag_names"]:
+            tag, _ = AKTag.objects.get_or_create(name=tag_name)
+            self.object.tags.add(tag)
+
+        return super_form_valid
+
 
 class AKOwnerCreateView(EventSlugMixin, CreateView):
     model = AKOwner
-- 
GitLab