From 92f0b464c3fcee85e7a885f4b9d7cf8fa80a58ac Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Sat, 17 May 2025 18:04:39 +0200
Subject: [PATCH 01/11] Migrate Event to CalendarEvent

---
 aleksis/apps/paweljong/forms.py               |  4 +-
 .../migrations/0033_migrate_event.py          | 44 +++++++++++++++++++
 .../migrations/0034_event_remove_fields.py    | 38 ++++++++++++++++
 aleksis/apps/paweljong/models.py              | 27 +++++++++---
 aleksis/apps/paweljong/schema/event.py        | 21 +++++++++
 aleksis/apps/paweljong/tables.py              |  2 +-
 .../templates/paweljong/event/detail.html     |  2 +-
 .../templates/paweljong/event/full.html       |  2 +-
 .../templates/paweljong/print/voucher.html    |  2 +-
 .../templated_email/event_created.email       |  4 +-
 aleksis/apps/paweljong/views.py               |  4 +-
 11 files changed, 135 insertions(+), 15 deletions(-)
 create mode 100644 aleksis/apps/paweljong/migrations/0033_migrate_event.py
 create mode 100644 aleksis/apps/paweljong/migrations/0034_event_remove_fields.py

diff --git a/aleksis/apps/paweljong/forms.py b/aleksis/apps/paweljong/forms.py
index 2c24b5e..c71478f 100644
--- a/aleksis/apps/paweljong/forms.py
+++ b/aleksis/apps/paweljong/forms.py
@@ -61,7 +61,7 @@ class EditEventForm(ExtensibleForm):
             "linked_group",
             Row("display_name", "slug", "description"),
             Row("place", "published"),
-            Fieldset(_("Date data"), Row("date_event", "date_registration", "date_retraction")),
+            Fieldset(_("Date data"), Row("date_start", "date_registration", "date_retraction")),
             Fieldset(
                 _("Event details"),
                 Row("cost", "min_cost", "max_cost", "max_participants"),
@@ -83,7 +83,7 @@ class EditEventForm(ExtensibleForm):
             "slug",
             "place",
             "published",
-            "date_event",
+            "date_start",
             "date_registration",
             "date_retraction",
             "cost",
diff --git a/aleksis/apps/paweljong/migrations/0033_migrate_event.py b/aleksis/apps/paweljong/migrations/0033_migrate_event.py
new file mode 100644
index 0000000..7a384f5
--- /dev/null
+++ b/aleksis/apps/paweljong/migrations/0033_migrate_event.py
@@ -0,0 +1,44 @@
+# Generated by Django 5.1.7 on 2025-04-03 11:42
+
+import aleksis.core.mixins
+import django.db.models.deletion
+from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.db import migrations, models
+
+
+def migrate_events(apps, schema_editor):
+    CalendarEvent = apps.get_model("core", "CalendarEvent")
+    Event = apps.get_model("paweljong", "Event")
+    event_ctype = ContentType.objects.get_for_model(Event)
+
+    db_alias = schema_editor.connection.alias
+
+    for event in Event.objects.using(db_alias).all():
+        calendar_event = CalendarEvent.objects.create(
+            date_start=event.date_event,
+            date_end=event.date_event,
+            polymorphic_ctype_id=event_ctype.pk,
+            extended_data=event.extended_data,
+            managed_by_app_label=event.managed_by_app_label,
+        )
+
+        event.calendarevent_ptr_id = calendar_event.pk
+        event.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('paweljong', '0032_alter_event_contact_information_visible_fields'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='event',
+            name='calendarevent_ptr',
+            field=models.OneToOneField(auto_created=True, null=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, serialize=False, to='core.calendarevent'),
+        ),
+        migrations.RunPython(migrate_events),
+    ]
diff --git a/aleksis/apps/paweljong/migrations/0034_event_remove_fields.py b/aleksis/apps/paweljong/migrations/0034_event_remove_fields.py
new file mode 100644
index 0000000..a753904
--- /dev/null
+++ b/aleksis/apps/paweljong/migrations/0034_event_remove_fields.py
@@ -0,0 +1,38 @@
+# Generated by Django 5.1.7 on 2025-04-03 11:42
+
+import aleksis.core.mixins
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('paweljong', '0033_migrate_event'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='event',
+            name='id',
+        ),
+        migrations.AlterField(
+            model_name='event',
+            name='calendarevent_ptr',
+            field=models.OneToOneField(auto_created=True, null=False, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.calendarevent'),
+        ),
+        migrations.RemoveField(
+            model_name='event',
+            name='extended_data',
+        ),
+        migrations.RemoveField(
+            model_name='event',
+            name='managed_by_app_label',
+        ),
+        migrations.RemoveField(
+            model_name='event',
+            name='date_event',
+        ),
+    ]
diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index 35deb2b..3a77722 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.fields import ArrayField
 from django.core.exceptions import ValidationError
 from django.db import models
+from django.http import HttpRequest
 from django.urls import reverse
 from django.utils.text import slugify
 from django.utils.timezone import now
@@ -16,7 +17,7 @@ from colorfield.fields import ColorField
 from aleksis.apps.tezor.models.base import Client
 from aleksis.apps.tezor.models.invoice import CustomPurchasedItem, Invoice, InvoiceGroup
 from aleksis.core.mixins import ExtensibleModel, GlobalPermissionModel
-from aleksis.core.models import Group, Person, PersonRelationship
+from aleksis.core.models import CalendarEvent, Group, Person, PersonRelationship
 from aleksis.core.util.core_helpers import generate_random_code, get_site_preferences
 from aleksis.core.util.email import send_email
 
@@ -151,7 +152,10 @@ class EventAdditionalField(ExtensibleModel):
         verbose_name_plural = _("Addtitional fields for events")
 
 
-class Event(ExtensibleModel):
+class Event(CalendarEvent):
+    _class_name = "paweljong_event"
+    dav_verbose_name = _("Event registrations")
+
     data_checks = [EventMembersSyncDataCheck]
 
     # Event details
@@ -165,7 +169,6 @@ class Event(ExtensibleModel):
     slug = models.SlugField(max_length=255, verbose_name=_("Slug"), blank=True)
 
     # Date details
-    date_event = models.DateField(verbose_name=_("Date of event"))
     date_registration = models.DateField(verbose_name=_("Registration deadline"))
     date_retraction = models.DateField(verbose_name=_("Retraction deadline"))
 
@@ -211,6 +214,20 @@ class Event(ExtensibleModel):
         default=[],
     )
 
+    @classmethod
+    def value_title(
+        cls, reference_object: "Event", request: HttpRequest | None = None
+    ) -> str:
+        """Return the title of the event."""
+        return reference_object.display_name
+
+    @classmethod
+    def value_description(
+        cls, reference_object: "Event", request: HttpRequest | None = None
+    ) -> str:
+        """Return the description of the event."""
+        return reference_object.description
+
     def save(self, *args, **kwargs):
         if not self.slug:
             if self.linked_group.short_name:
@@ -250,7 +267,7 @@ class Event(ExtensibleModel):
 
         if self.date_registration:
             return self.date_registration >= now
-        return self.date_event > now
+        return self.date_start > now
 
     def get_absolute_url(self):
         return reverse("event_by_name", kwargs={"slug": self.slug})
@@ -281,7 +298,7 @@ class Event(ExtensibleModel):
 
     @classmethod
     def upcoming_published_events(cls):
-        return Event.objects.filter(published=True, date_event__gte=now())
+        return Event.objects.filter(published=True, date_start=now())
 
 
 class EventInfoMailingThrough(ExtensibleModel):
diff --git a/aleksis/apps/paweljong/schema/event.py b/aleksis/apps/paweljong/schema/event.py
index 539969c..33ea772 100644
--- a/aleksis/apps/paweljong/schema/event.py
+++ b/aleksis/apps/paweljong/schema/event.py
@@ -8,3 +8,24 @@ from ..models import Event
 class EventType(PermissionsTypeMixin, DjangoObjectType):
     class Meta:
         model = Event
+        fields = [
+            "id",
+            "uuid",
+            "date_start",
+            "date_end",
+            "display_name",
+            "description",
+            "published",
+            "place",
+            "slug",
+            "cost",
+            "min_cost",
+            "max_cost",
+            "max_participants",
+            "date_registration",
+            "date_retraction",
+            "terms",
+            "information",
+            "additional_fields",
+            "contact_information_visible_fields",
+        ]
diff --git a/aleksis/apps/paweljong/tables.py b/aleksis/apps/paweljong/tables.py
index 98e870e..0643a46 100644
--- a/aleksis/apps/paweljong/tables.py
+++ b/aleksis/apps/paweljong/tables.py
@@ -10,7 +10,7 @@ class ManageEventsTable(tables.Table):
         attrs = {"class": "responsive-table highlight"}
 
     display_name = tables.Column(verbose_name=_("Event"))
-    date_event = tables.Column(verbose_name=_("Date"))
+    date_start = tables.Column(verbose_name=_("Date"))
     max_participants = tables.Column(verbose_name=_("Max. participants"))
     date_registration = tables.Column(verbose_name=_("Registration until"))
 
diff --git a/aleksis/apps/paweljong/templates/paweljong/event/detail.html b/aleksis/apps/paweljong/templates/paweljong/event/detail.html
index 433537a..1482b77 100644
--- a/aleksis/apps/paweljong/templates/paweljong/event/detail.html
+++ b/aleksis/apps/paweljong/templates/paweljong/event/detail.html
@@ -38,7 +38,7 @@
         <table id="event-detail-table">
           <tr>
             <td><i class="material-icons small">event</i></td>
-            <td>{{ event.date_event }}</td>
+            <td>{{ event.date_start }}</td>
             <td><i class="material-icons small">location_on</i></td>
             <td>{{ event.place }}</td>
           </tr>
diff --git a/aleksis/apps/paweljong/templates/paweljong/event/full.html b/aleksis/apps/paweljong/templates/paweljong/event/full.html
index 06b637b..28ccab4 100644
--- a/aleksis/apps/paweljong/templates/paweljong/event/full.html
+++ b/aleksis/apps/paweljong/templates/paweljong/event/full.html
@@ -22,7 +22,7 @@
           <table>
             <tr>
               <td><i class="material-icons small">event</i></td>
-              <td>{{ event.date_event }}</td>
+              <td>{{ event.date_start }}</td>
               <td><i class="material-icons small">location_on</i></td>
               <td>{{ event.place }}</td>
             </tr>
diff --git a/aleksis/apps/paweljong/templates/paweljong/print/voucher.html b/aleksis/apps/paweljong/templates/paweljong/print/voucher.html
index 5648732..8500d1b 100644
--- a/aleksis/apps/paweljong/templates/paweljong/print/voucher.html
+++ b/aleksis/apps/paweljong/templates/paweljong/print/voucher.html
@@ -17,7 +17,7 @@
         <img src="{% firstof request.site.preferences.theme__logo.url aleksis_banner %}" alt="Logo" class="max-size-600 center">
         <h3>{{ voucher.event }}<h3>
         <h4>{{ voucher.code }}</h4>
-        <p>{% trans "Voucher for" %} {{ voucher.person.first_name }} {{ voucher.person.last_name }} {% trans "to visit event" %} {{ voucher.event }} {% trans "on" %} {{ voucher.event.date_event }} {% trans "at" %} {{ voucher.event.place }}!</p>
+        <p>{% trans "Voucher for" %} {{ voucher.person.first_name }} {{ voucher.person.last_name }} {% trans "to visit event" %} {{ voucher.event }} {% trans "on" %} {{ voucher.event.date_start }} {% trans "at" %} {{ voucher.event.place }}!</p>
         <p>{% trans "To use the voucher, register for the event " %} <a href="{% url 'register_event_by_slug' voucher.event.pk %}">{% trans "here" %}</a></p>
     </div>
 
diff --git a/aleksis/apps/paweljong/templates/templated_email/event_created.email b/aleksis/apps/paweljong/templates/templated_email/event_created.email
index 30ac030..77d97d2 100644
--- a/aleksis/apps/paweljong/templates/templated_email/event_created.email
+++ b/aleksis/apps/paweljong/templates/templated_email/event_created.email
@@ -9,7 +9,7 @@
     * {% trans "Description" %}: {{ new_event.description }}
     * {% trans "Published" %}: {{ new_event.published }}
     * {% trans "Place" %}: {{ new_event.place }}
-    * {% trans "Date of event" %}: {{ new_event.date_event }}
+    * {% trans "Date of event" %}: {{ new_event.date_start }}
     * {% trans "Registration deadline" %}: {{ new_event.date_registration }}
     * {% trans "Retraction deadline" %}: {{ new_event.date_retraction }}
     * {% trans "Fees" %}: {{ new_event.cost }}
@@ -39,7 +39,7 @@
             <li> {% trans "Description" %}: {{ new_event.description }}</li>
             <li> {% trans "Published" %}: {{ new_event.published }}</li>
             <li> {% trans "Place" %}: {{ new_event.place }}</li>
-            <li> {% trans "Date of event" %}: {{ new_event.date_event }}</li>
+            <li> {% trans "Date of event" %}: {{ new_event.date_start }}</li>
             <li> {% trans "Registration deadline" %}: {{ new_event.date_registration }}</li>
             <li> {% trans "Retraction deadline" %}: {{ new_event.date_retraction }}</li>
             <li> {% trans "Fees" %}: {{ new_event.cost }}</li>
diff --git a/aleksis/apps/paweljong/views.py b/aleksis/apps/paweljong/views.py
index 353a0ab..09cecc6 100644
--- a/aleksis/apps/paweljong/views.py
+++ b/aleksis/apps/paweljong/views.py
@@ -823,10 +823,10 @@ class UpcomingEventsRSSFeed(Feed):
         return _("Announcement feed of all upcoming events")
 
     def ttl(self):
-        date_event = Event.upcoming_published_events().order_by("-date_event").first().date_event
+        date_start = Event.upcoming_published_events().order_by("-date_start").first().date_start
         date_now = timezone.now().date()
 
-        return (date_event - date_now).seconds
+        return (date_start - date_now).seconds
 
     def items(self):
         return Event.upcoming_published_events()
-- 
GitLab


From c1e8c57465eeccfb944beb7b1acf01893f6cff1b Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Sat, 17 May 2025 18:04:54 +0200
Subject: [PATCH 02/11] Adapt to UUIDs

---
 .../paweljong/migrations/0035_add_uuid.py     | 53 +++++++++++++++++++
 1 file changed, 53 insertions(+)
 create mode 100644 aleksis/apps/paweljong/migrations/0035_add_uuid.py

diff --git a/aleksis/apps/paweljong/migrations/0035_add_uuid.py b/aleksis/apps/paweljong/migrations/0035_add_uuid.py
new file mode 100644
index 0000000..8f901f6
--- /dev/null
+++ b/aleksis/apps/paweljong/migrations/0035_add_uuid.py
@@ -0,0 +1,53 @@
+# Generated by Django 5.2.1 on 2025-05-17 15:43
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('paweljong', '0034_event_remove_fields'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='checkpoint',
+            name='uuid',
+            field=models.UUIDField(db_default=models.Func(function='gen_random_uuid'), editable=False, unique=True),
+        ),
+        migrations.AddField(
+            model_name='eventadditionalfield',
+            name='uuid',
+            field=models.UUIDField(db_default=models.Func(function='gen_random_uuid'), editable=False, unique=True),
+        ),
+        migrations.AddField(
+            model_name='eventinfomailingthrough',
+            name='uuid',
+            field=models.UUIDField(db_default=models.Func(function='gen_random_uuid'), editable=False, unique=True),
+        ),
+        migrations.AddField(
+            model_name='eventregistration',
+            name='uuid',
+            field=models.UUIDField(db_default=models.Func(function='gen_random_uuid'), editable=False, unique=True),
+        ),
+        migrations.AddField(
+            model_name='infomailing',
+            name='uuid',
+            field=models.UUIDField(db_default=models.Func(function='gen_random_uuid'), editable=False, unique=True),
+        ),
+        migrations.AddField(
+            model_name='registrationstate',
+            name='uuid',
+            field=models.UUIDField(db_default=models.Func(function='gen_random_uuid'), editable=False, unique=True),
+        ),
+        migrations.AddField(
+            model_name='terms',
+            name='uuid',
+            field=models.UUIDField(db_default=models.Func(function='gen_random_uuid'), editable=False, unique=True),
+        ),
+        migrations.AddField(
+            model_name='voucher',
+            name='uuid',
+            field=models.UUIDField(db_default=models.Func(function='gen_random_uuid'), editable=False, unique=True),
+        ),
+    ]
-- 
GitLab


From 5ceb73bd0b0fac041a5875d21b91dfccc98af3e8 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Sun, 18 May 2025 16:01:37 +0200
Subject: [PATCH 03/11] Show only relevant event

---
 aleksis/apps/paweljong/models.py | 37 ++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index 3a77722..f8a5623 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -1,10 +1,12 @@
 from datetime import datetime, timedelta
 from decimal import Decimal
+from typing import Optional
 
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.postgres.fields import ArrayField
 from django.core.exceptions import ValidationError
 from django.db import models
+from django.db.models import Q, QuerySet
 from django.http import HttpRequest
 from django.urls import reverse
 from django.utils.text import slugify
@@ -228,6 +230,41 @@ class Event(CalendarEvent):
         """Return the description of the event."""
         return reference_object.description
 
+    @classmethod
+    def get_objects(
+        cls,
+        request: HttpRequest | None = None,
+        params: dict[str, any] | None = None,
+        start: Optional[datetime] = None,
+        end: Optional[datetime] = None,
+        start_qs: QuerySet | None = None,
+        additional_filter: Q | None = None,
+        **kwargs,
+    ) -> QuerySet:
+        q = additional_filter if additional_filter is not None else Q()
+        if request:
+            q = q & (
+                Q(
+                    id__in=EventRegistration.objects.filter(person=request.user.person)
+                    .values_list("event_id", flat=True)
+                    .union(
+                        Group.objects.filter(linked_event__isnull=False, owners=request.user.person)
+                        .values_list("linked_event__id", flat=True)
+                    )
+                )
+            )
+
+        qs = super().get_objects(
+            request=request,
+            params=params,
+            start=start,
+            end=end,
+            start_qs=start_qs,
+            additional_filter=q,
+            **kwargs,
+        )
+        return qs
+
     def save(self, *args, **kwargs):
         if not self.slug:
             if self.linked_group.short_name:
-- 
GitLab


From 6a7a5cbac27483d52fdf99bf77df35308a43218b Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Sun, 18 May 2025 16:29:52 +0200
Subject: [PATCH 04/11] Migrate event registration references

---
 aleksis/apps/paweljong/migrations/0033_migrate_event.py  | 9 +++++++++
 .../paweljong/migrations/0034_event_remove_fields.py     | 5 +++++
 2 files changed, 14 insertions(+)

diff --git a/aleksis/apps/paweljong/migrations/0033_migrate_event.py b/aleksis/apps/paweljong/migrations/0033_migrate_event.py
index 7a384f5..ca511c6 100644
--- a/aleksis/apps/paweljong/migrations/0033_migrate_event.py
+++ b/aleksis/apps/paweljong/migrations/0033_migrate_event.py
@@ -23,6 +23,10 @@ def migrate_events(apps, schema_editor):
             managed_by_app_label=event.managed_by_app_label,
         )
 
+        for registration in event.registrations.all():
+            registration.event_id = calendar_event.pk
+            registration.save()
+
         event.calendarevent_ptr_id = calendar_event.pk
         event.save()
 
@@ -40,5 +44,10 @@ class Migration(migrations.Migration):
             name='calendarevent_ptr',
             field=models.OneToOneField(auto_created=True, null=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, serialize=False, to='core.calendarevent'),
         ),
+        migrations.AlterField(
+            model_name='eventregistration',
+            name='event',
+            field=models.ForeignKey(db_constraint=False, db_index=False, on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='paweljong.event', verbose_name='Event'),
+        ),
         migrations.RunPython(migrate_events),
     ]
diff --git a/aleksis/apps/paweljong/migrations/0034_event_remove_fields.py b/aleksis/apps/paweljong/migrations/0034_event_remove_fields.py
index a753904..a1f5c67 100644
--- a/aleksis/apps/paweljong/migrations/0034_event_remove_fields.py
+++ b/aleksis/apps/paweljong/migrations/0034_event_remove_fields.py
@@ -35,4 +35,9 @@ class Migration(migrations.Migration):
             model_name='event',
             name='date_event',
         ),
+        migrations.AlterField(
+            model_name='eventregistration',
+            name='event',
+            field=models.ForeignKey(db_constraint=False, db_index=False, on_delete=django.db.models.deletion.CASCADE, related_name='registrations', to='paweljong.event', verbose_name='Event'),
+        ),
     ]
-- 
GitLab


From 91b509f22c07d4872328bab2bbc94cade3c24767 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Sun, 18 May 2025 22:32:20 +0200
Subject: [PATCH 05/11] Include attendees in ical

---
 aleksis/apps/paweljong/models.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index f8a5623..e20256a 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -15,6 +15,7 @@ from django.utils.translation import gettext_lazy as _
 
 from ckeditor.fields import RichTextField
 from colorfield.fields import ColorField
+from icalendar import vCalAddress
 
 from aleksis.apps.tezor.models.base import Client
 from aleksis.apps.tezor.models.invoice import CustomPurchasedItem, Invoice, InvoiceGroup
@@ -230,6 +231,21 @@ class Event(CalendarEvent):
         """Return the description of the event."""
         return reference_object.description
 
+    @classmethod
+    def value_attendee(
+        cls, reference_object: "Event", request: HttpRequest | None = None
+    ) -> list[vCalAddress]:
+        """Return the attendees of the event."""
+        owners = Person.objects.filter(owner_of=reference_object.linked_group)
+        participants = Person.objects.filter(
+            pk__in=reference_object.registrations.filter(retracted=False).values_list("person__id", flat=True)
+        )
+
+        owner_attendees = [p.get_vcal_address(request=request) for p in owners]
+        participant_attendees = [p.get_vcal_address(role="OPT-PARTICIPANT", request=request) for p in participants]
+
+        return owner_attendees + participant_attendees
+
     @classmethod
     def get_objects(
         cls,
-- 
GitLab


From b22724da056f68718196549b079737f7d04ea773 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Sun, 18 May 2025 22:32:36 +0200
Subject: [PATCH 06/11] Set ical status based on event registration retraction

---
 aleksis/apps/paweljong/models.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index e20256a..4b22639 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -246,6 +246,22 @@ class Event(CalendarEvent):
 
         return owner_attendees + participant_attendees
 
+    @classmethod
+    def value_status(
+        cls, reference_object: "Event", request: HttpRequest | None = None
+    ) -> str:
+        """Return the status of the event."""
+        if request is None:
+            return "CONFIRMED"
+        try:
+            registration = EventRegistration.objects.get(
+                event=reference_object,
+                person=request.user.person,
+            )
+        except EventRegistration.DoesNotExist:
+            return "CONFIRMED"
+        return "CANCELLED" if registration.retracted else "CONFIRMED"
+
     @classmethod
     def get_objects(
         cls,
-- 
GitLab


From 00a80b58b98df64882ab87dd4267cd63bc010c95 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Thu, 22 May 2025 09:54:44 +0200
Subject: [PATCH 07/11] Fix refactoring mistake

---
 aleksis/apps/paweljong/models.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index 4b22639..8964a1c 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -367,7 +367,7 @@ class Event(CalendarEvent):
 
     @classmethod
     def upcoming_published_events(cls):
-        return Event.objects.filter(published=True, date_start=now())
+        return Event.objects.filter(published=True, date_start__gte=now())
 
 
 class EventInfoMailingThrough(ExtensibleModel):
-- 
GitLab


From 4dd0379698de7811310edc1502f456cd1e5522d0 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Thu, 22 May 2025 19:10:19 +0200
Subject: [PATCH 08/11] Show participants only to event owners

---
 aleksis/apps/paweljong/models.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index 8964a1c..912dd22 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -236,6 +236,11 @@ class Event(CalendarEvent):
         cls, reference_object: "Event", request: HttpRequest | None = None
     ) -> list[vCalAddress]:
         """Return the attendees of the event."""
+        if request is None:
+            return []
+        is_owner = reference_object.linked_group.owners.filter(user_id=request.user.pk).exists()
+        if not is_owner:
+            return []
         owners = Person.objects.filter(owner_of=reference_object.linked_group)
         participants = Person.objects.filter(
             pk__in=reference_object.registrations.filter(retracted=False).values_list("person__id", flat=True)
-- 
GitLab


From b90bc07499f30b7b8cef5603e06d93929778e015 Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Thu, 22 May 2025 19:20:09 +0200
Subject: [PATCH 09/11] Migrate references to event

---
 .../migrations/0033_migrate_event.py          | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/aleksis/apps/paweljong/migrations/0033_migrate_event.py b/aleksis/apps/paweljong/migrations/0033_migrate_event.py
index ca511c6..2a8e8fa 100644
--- a/aleksis/apps/paweljong/migrations/0033_migrate_event.py
+++ b/aleksis/apps/paweljong/migrations/0033_migrate_event.py
@@ -27,6 +27,26 @@ def migrate_events(apps, schema_editor):
             registration.event_id = calendar_event.pk
             registration.save()
 
+        for term in event.terms.all():
+            term.event_id = calendar_event.pk
+            term.save()
+
+        for info_mailing in event.info_mailings.all():
+            info_mailing.event_id = calendar_event.pk
+            info_mailing.save()
+
+        for additional_field in event.additional_fields.all():
+            additional_field.event_id = calendar_event.pk
+            additional_field.save()
+
+        for voucher in event.vouchers.all():
+            voucher.event_id = calendar_event.pk
+            voucher.save()
+
+        for checkpoint in event.checkpoints.all():
+            checkpoint.event_id = calendar_event.pk
+            checkpoint.save()
+
         event.calendarevent_ptr_id = calendar_event.pk
         event.save()
 
-- 
GitLab


From 8c5c52929eaf944d8fa49bcc103ce2fe966a7463 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Sun, 1 Jun 2025 13:12:00 +0200
Subject: [PATCH 10/11] Add date_end to event form

---
 aleksis/apps/paweljong/forms.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/aleksis/apps/paweljong/forms.py b/aleksis/apps/paweljong/forms.py
index c71478f..0d12959 100644
--- a/aleksis/apps/paweljong/forms.py
+++ b/aleksis/apps/paweljong/forms.py
@@ -61,7 +61,10 @@ class EditEventForm(ExtensibleForm):
             "linked_group",
             Row("display_name", "slug", "description"),
             Row("place", "published"),
-            Fieldset(_("Date data"), Row("date_start", "date_registration", "date_retraction")),
+            Fieldset(
+                _("Date data"),
+                Row("date_start", "date_end", "date_registration", "date_retraction"),
+            ),
             Fieldset(
                 _("Event details"),
                 Row("cost", "min_cost", "max_cost", "max_participants"),
@@ -84,6 +87,7 @@ class EditEventForm(ExtensibleForm):
             "place",
             "published",
             "date_start",
+            "date_end",
             "date_registration",
             "date_retraction",
             "cost",
-- 
GitLab


From c8ef29d26512126945166622bae8b85b809b8a67 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Sun, 1 Jun 2025 13:12:21 +0200
Subject: [PATCH 11/11] Reformat

---
 .../EventRegistrationForm.vue                 | 33 +++++++++-------
 aleksis/apps/paweljong/models.py              | 22 +++++------
 .../paweljong/schema/event_registration.py    | 39 +++++++++++++++----
 aleksis/apps/paweljong/views.py               |  4 +-
 4 files changed, 65 insertions(+), 33 deletions(-)

diff --git a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
index 558164c..5fe372d 100644
--- a/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
+++ b/aleksis/apps/paweljong/frontend/components/event_registration/EventRegistrationForm.vue
@@ -320,7 +320,12 @@ import EventRegistrationHelpTextCards from "./EventRegistrationHelpTextCards.vue
                           )
                         "
                         required
-                        :rules="$rules().required.build([ ...usernameRules.usernameAllowed, ...usernameRules.usernameASCII ])"
+                        :rules="
+                          $rules().required.build([
+                            ...usernameRules.usernameAllowed,
+                            ...usernameRules.usernameASCII,
+                          ])
+                        "
                         prepend-icon="mdi-account-outline"
                       ></v-text-field>
                     </div>
@@ -702,8 +707,13 @@ import EventRegistrationHelpTextCards from "./EventRegistrationHelpTextCards.vue
                       </v-col>
                     </v-row>
                   </v-card-text>
-                  <v-card-actions v-if="index > 0 && !Object.hasOwn(guardian, 'id')">
-                    <cancel-button i18n-key="paweljong.event_registration.form.steps.guardians.remove" @click="removeGuardian(index)" />
+                  <v-card-actions
+                    v-if="index > 0 && !Object.hasOwn(guardian, 'id')"
+                  >
+                    <cancel-button
+                      i18n-key="paweljong.event_registration.form.steps.guardians.remove"
+                      @click="removeGuardian(index)"
+                    />
                   </v-card-actions>
                 </v-card>
               </v-form>
@@ -1046,13 +1056,8 @@ export default {
       query: whoAmI,
       result({ data }) {
         if (data && data.whoAmI && data.whoAmI.person) {
-          const {
-            id,
-            __typename,
-            addresses,
-            guardians,
-            ...filteredPerson
-          } = data.whoAmI.person;
+          const { id, __typename, addresses, guardians, ...filteredPerson } =
+            data.whoAmI.person;
           this.data.person = filteredPerson;
 
           const filteredGuardians = guardians.map(
@@ -1071,7 +1076,9 @@ export default {
             ];
           }
 
-          const defaultAddress = addresses.find((a) => a.addressTypes.some((at) => at.name === "default"));
+          const defaultAddress = addresses.find((a) =>
+            a.addressTypes.some((at) => at.name === "default"),
+          );
           if (defaultAddress) {
             this.data.person.address = {
               street: defaultAddress.street,
@@ -1080,7 +1087,7 @@ export default {
               place: defaultAddress.place,
               country: defaultAddress.country,
             };
-	  } else {
+          } else {
             this.data.person.address = {
               street: "",
               housenumber: "",
@@ -1088,7 +1095,7 @@ export default {
               place: "",
               country: "",
             };
-	  }
+          }
         }
       },
       skip() {
diff --git a/aleksis/apps/paweljong/models.py b/aleksis/apps/paweljong/models.py
index 912dd22..833e037 100644
--- a/aleksis/apps/paweljong/models.py
+++ b/aleksis/apps/paweljong/models.py
@@ -218,9 +218,7 @@ class Event(CalendarEvent):
     )
 
     @classmethod
-    def value_title(
-        cls, reference_object: "Event", request: HttpRequest | None = None
-    ) -> str:
+    def value_title(cls, reference_object: "Event", request: HttpRequest | None = None) -> str:
         """Return the title of the event."""
         return reference_object.display_name
 
@@ -243,18 +241,20 @@ class Event(CalendarEvent):
             return []
         owners = Person.objects.filter(owner_of=reference_object.linked_group)
         participants = Person.objects.filter(
-            pk__in=reference_object.registrations.filter(retracted=False).values_list("person__id", flat=True)
+            pk__in=reference_object.registrations.filter(retracted=False).values_list(
+                "person__id", flat=True
+            )
         )
 
         owner_attendees = [p.get_vcal_address(request=request) for p in owners]
-        participant_attendees = [p.get_vcal_address(role="OPT-PARTICIPANT", request=request) for p in participants]
+        participant_attendees = [
+            p.get_vcal_address(role="OPT-PARTICIPANT", request=request) for p in participants
+        ]
 
         return owner_attendees + participant_attendees
 
     @classmethod
-    def value_status(
-        cls, reference_object: "Event", request: HttpRequest | None = None
-    ) -> str:
+    def value_status(cls, reference_object: "Event", request: HttpRequest | None = None) -> str:
         """Return the status of the event."""
         if request is None:
             return "CONFIRMED"
@@ -285,8 +285,9 @@ class Event(CalendarEvent):
                     id__in=EventRegistration.objects.filter(person=request.user.person)
                     .values_list("event_id", flat=True)
                     .union(
-                        Group.objects.filter(linked_event__isnull=False, owners=request.user.person)
-                        .values_list("linked_event__id", flat=True)
+                        Group.objects.filter(
+                            linked_event__isnull=False, owners=request.user.person
+                        ).values_list("linked_event__id", flat=True)
                     )
                 )
             )
@@ -515,7 +516,6 @@ class EventRegistration(ExtensibleModel):
             },
         )
 
-
         address = self.person.addresses.first()
         street = address.street if address is not None else ""
         housenumber = address.housenumber if address is not None else ""
diff --git a/aleksis/apps/paweljong/schema/event_registration.py b/aleksis/apps/paweljong/schema/event_registration.py
index 3c2d665..493cf12 100644
--- a/aleksis/apps/paweljong/schema/event_registration.py
+++ b/aleksis/apps/paweljong/schema/event_registration.py
@@ -14,7 +14,11 @@ from aleksis.apps.postbuero.models import MailAddress, MailDomain
 from aleksis.apps.postbuero.schema import MailAddressInputType
 from aleksis.core.models import Activity, Person
 from aleksis.core.schema.base import PermissionsTypeMixin
-from aleksis.core.schema.person import PersonAddressMutationMixin, PersonGuardianMutationMixin, PersonInputType
+from aleksis.core.schema.person import (
+    PersonAddressMutationMixin,
+    PersonGuardianMutationMixin,
+    PersonInputType,
+)
 from aleksis.core.schema.user import UserInputType
 from aleksis.core.util.auth_helpers import custom_username_validators
 from aleksis.core.util.core_helpers import get_site_preferences
@@ -50,7 +54,9 @@ class EventRegistrationInputType(graphene.InputObjectType):
     retraction_consent = graphene.Boolean(required=True)
 
 
-class SendEventRegistrationMutation(PersonAddressMutationMixin, PersonGuardianMutationMixin, graphene.Mutation):
+class SendEventRegistrationMutation(
+    PersonAddressMutationMixin, PersonGuardianMutationMixin, graphene.Mutation
+):
     class Arguments:
         event = graphene.ID(required=True)
         event_registration = EventRegistrationInputType(required=True)
@@ -75,7 +81,13 @@ class SendEventRegistrationMutation(PersonAddressMutationMixin, PersonGuardianMu
 
         email = None
 
-        if event_registration["email"] is not None and event_registration["email"]["domain"] is not None and event_registration["email"]["domain"] != "" and event_registration["email"]["local_part"] is not None and event_registration["email"]["local_part"] != "":
+        if (
+            event_registration["email"] is not None
+            and event_registration["email"]["domain"] is not None
+            and event_registration["email"]["domain"] != ""
+            and event_registration["email"]["local_part"] is not None
+            and event_registration["email"]["local_part"] != ""
+        ):
             try:
                 domain = MailDomain.objects.get(pk=event_registration["email"]["domain"])
             except IntegrityError:
@@ -89,7 +101,11 @@ class SendEventRegistrationMutation(PersonAddressMutationMixin, PersonGuardianMu
                 raise ValidationError(_("Mail address already in use."))
 
             email = str(_mail_address)
-        elif event_registration["user"] is not None and event_registration["user"]["email"] is not None and event_registration["user"]["email"] != "":
+        elif (
+            event_registration["user"] is not None
+            and event_registration["user"]["email"] is not None
+            and event_registration["user"]["email"] != ""
+        ):
             validate_email(event_registration["user"]["email"])
             email = event_registration["user"]["email"]
         elif not info.context.user.is_authenticated:
@@ -99,7 +115,10 @@ class SendEventRegistrationMutation(PersonAddressMutationMixin, PersonGuardianMu
         if info.context.user.is_authenticated:
             user = info.context.user
         elif event_registration["user"] is not None:
-            if event_registration["user"]["username"] is not None and event_registration["user"]["username"] != "":
+            if (
+                event_registration["user"]["username"] is not None
+                and event_registration["user"]["username"] != ""
+            ):
                 for validator in custom_username_validators:
                     validator(event_registration["user"]["username"])
                 try:
@@ -112,7 +131,10 @@ class SendEventRegistrationMutation(PersonAddressMutationMixin, PersonGuardianMu
             else:
                 raise ValidationError(_("Username is required"))
 
-            if event_registration["user"]["password"] is not None and event_registration["user"]["password"] != "":
+            if (
+                event_registration["user"]["password"] is not None
+                and event_registration["user"]["password"] != ""
+            ):
                 user.set_password(event_registration["user"]["password"])
             else:
                 raise ValidationError(_("Password is required"))
@@ -147,7 +169,10 @@ class SendEventRegistrationMutation(PersonAddressMutationMixin, PersonGuardianMu
         guardian_pks = []
 
         # Store address information
-        if event_registration["person"] is not None and event_registration["person"]["address"] is not None:
+        if (
+            event_registration["person"] is not None
+            and event_registration["person"]["address"] is not None
+        ):
             cls._handle_address(root, info, event_registration["person"]["address"], person)
 
         if event_registration["person"] is not None:
diff --git a/aleksis/apps/paweljong/views.py b/aleksis/apps/paweljong/views.py
index 09cecc6..cee8533 100644
--- a/aleksis/apps/paweljong/views.py
+++ b/aleksis/apps/paweljong/views.py
@@ -1,11 +1,11 @@
 from typing import Optional
 
-from django.db.models import Avg
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.syndication.views import Feed
 from django.core.exceptions import ValidationError
+from django.db.models import Avg
 from django.http import HttpRequest, HttpResponse
 from django.shortcuts import redirect, render
 from django.urls import reverse, reverse_lazy
@@ -1014,7 +1014,7 @@ class EventDetailView(PermissionRequiredMixin, DetailView):
 
         average_amount_paid = Invoice.objects.filter(
             for_content_type=ContentType.objects.get_for_model(EventRegistration),
-            for_object_id__in=active_registrations.values_list("id", flat=True)
+            for_object_id__in=active_registrations.values_list("id", flat=True),
         ).aggregate(Avg("total", default=0))["total__avg"]
         context["average_amount_paid"] = round(average_amount_paid, 2)
         context["average_amount_paid_percent"] = (
-- 
GitLab