diff --git a/AKModel/admin.py b/AKModel/admin.py
index 56833b20bfdd2471e0bb52fe0e94291981dbb79c..0b17c5523d0e26263bd5487e72dd8b2796b6bdb6 100644
--- a/AKModel/admin.py
+++ b/AKModel/admin.py
@@ -17,7 +17,7 @@ from simple_history.admin import SimpleHistoryAdmin
 from AKModel.availability.models import Availability
 from AKModel.forms import RoomFormWithAvailabilities
 from AKModel.models import Event, AKOwner, AKCategory, AKTrack, AKRequirement, AK, AKSlot, Room, AKOrgaMessage, \
-    ConstraintViolation, DefaultSlot, AKType
+    ConstraintViolation, DefaultSlot, AKType, EventParticipant, AKPreference
 from AKModel.urls import get_admin_urls_event_wizard, get_admin_urls_event
 from AKModel.views.ak import AKResetInterestView, AKResetInterestCounterView
 from AKModel.views.manage import CVMarkResolvedView, CVSetLevelViolationView, CVSetLevelWarningView
@@ -572,6 +572,50 @@ class DefaultSlotAdmin(EventTimezoneFormMixin, admin.ModelAdmin):
     form = DefaultSlotAdminForm
 
 
+@admin.register(EventParticipant)
+class EventParticipantAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
+    """
+    Admin interface for EventParticipant
+    """
+    model = EventParticipant
+    list_display = ['name', 'institution', 'event']
+    list_filter = ['event', 'institution']
+    list_editable = []
+    ordering = ['name']
+
+
+class AKPreferenceAdminForm(forms.ModelForm):
+    """
+    Adapted admin form for AK preferences for usage in :class:`AKPreferenceAdmin`)
+    """
+    class Meta:
+        widgets = {
+            'participant': forms.Select,
+            'slot': forms.Select,
+        }
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        # Filter possible values for foreign keys & m2m when event is specified
+        if hasattr(self.instance, "event") and self.instance.event is not None:
+            self.fields["participant"].queryset = EventParticipant.objects.filter(event=self.instance.event)
+            self.fields["slot"].queryset = AKSlot.objects.filter(event=self.instance.event)
+
+
+@admin.register(AKPreference)
+class AKPreferenceAdmin(PrepopulateWithNextActiveEventMixin, admin.ModelAdmin):
+    """
+    Admin interface for AK preferences.
+    Uses an adapted form (see :class:`AKPreferenceAdminForm`)
+    """
+    model = AKPreference
+    form = AKPreferenceAdminForm
+    list_display = ['preference', 'participant', 'slot', 'event']
+    list_filter = ['event', 'slot', 'participant']
+    list_editable = []
+    ordering = ['participant', 'preference', 'slot']
+
+
 # Define a new User admin
 class UserAdmin(BaseUserAdmin):
     """
diff --git a/AKModel/availability/models.py b/AKModel/availability/models.py
index e2a64b225d2ca4a3da117003e29f5ff399a76976..e58ba070c574d8d5601ce5665bd19398827e3c6e 100644
--- a/AKModel/availability/models.py
+++ b/AKModel/availability/models.py
@@ -10,7 +10,7 @@ from django.db import models
 from django.utils.functional import cached_property
 from django.utils.translation import gettext_lazy as _
 
-from AKModel.models import Event, AKOwner, Room, AK, AKCategory
+from AKModel.models import Event, AKOwner, Room, AK, AKCategory, EventParticipant
 
 zero_time = datetime.time(0, 0)
 
@@ -24,6 +24,7 @@ zero_time = datetime.time(0, 0)
 # enable availabilities for AKs and AKCategories
 # add verbose names and help texts to model attributes
 # adapt or extemd documentation
+# add participants
 
 
 class Availability(models.Model):
@@ -79,20 +80,48 @@ class Availability(models.Model):
         verbose_name=_('AK Category'),
         help_text=_('AK Category whose availability this is'),
     )
+    participant = models.ForeignKey(
+        to=EventParticipant,
+        related_name='availabilities',
+        on_delete=models.CASCADE,
+        null=True,
+        blank=True,
+        verbose_name=_('Participant'),
+        help_text=_('Participant whose availability this is'),
+    )
     start = models.DateTimeField()
     end = models.DateTimeField()
 
     def __str__(self) -> str:
         person = self.person.name if self.person else None
+        participant = self.participant.name if self.participant else None
         room = getattr(self.room, 'name', None)
         event = getattr(getattr(self, 'event', None), 'name', None)
         ak = getattr(self.ak, 'name', None)
         ak_category = getattr(self.ak_category, 'name', None)
-        return f'Availability(event={event}, person={person}, room={room}, ak={ak}, ak category={ak_category})'
+        arg_list = [
+            f"event={event}",
+            f"person={person}",
+            f"room={room}",
+            f"ak={ak}",
+            f"ak category={ak_category}",
+            f"participant={participant}",
+        ]
+        return f'Availability({", ".join(arg_list)})'
 
     def __hash__(self):
         return hash(
-            (getattr(self, 'event', None), self.person, self.room, self.ak, self.ak_category, self.start, self.end))
+            (
+                getattr(self, 'event', None),
+                self.person,
+                self.room,
+                self.ak,
+                self.ak_category,
+                self.participant,
+                self.start,
+                self.end,
+            )
+        )
 
     def __eq__(self, other: 'Availability') -> bool:
         """Comparisons like ``availability1 == availability2``.
@@ -103,7 +132,7 @@ class Availability(models.Model):
         return all(
             (
                 getattr(self, attribute, None) == getattr(other, attribute, None)
-                for attribute in ['event', 'person', 'room', 'ak', 'ak_category', 'start', 'end']
+                for attribute in ['event', 'person', 'room', 'ak', 'ak_category', 'participant', 'start', 'end']
             )
         )
 
@@ -260,6 +289,7 @@ class Availability(models.Model):
         room: Room | None = None,
         ak: AK | None = None,
         ak_category: AKCategory | None = None,
+        participant: EventParticipant | None = None,
     ) -> "Availability":
         """
         Create an availability covering exactly the time between event start and event end.
@@ -278,7 +308,7 @@ class Availability(models.Model):
         timeframe_end = event.end  # adapt to our event model
         timeframe_end = timeframe_end + datetime.timedelta(days=1)
         return Availability(start=timeframe_start, end=timeframe_end, event=event, person=person,
-                                    room=room, ak=ak, ak_category=ak_category)
+                                    room=room, ak=ak, ak_category=ak_category, participant=participant)
 
     def is_covered(self, availabilities: List['Availability']):
         """Check if list of availibilities cover this object.
diff --git a/AKModel/locale/de_DE/LC_MESSAGES/django.po b/AKModel/locale/de_DE/LC_MESSAGES/django.po
index a489e73bee123c50d73e91683a90ce4d7df5b886..297ebfad2a3427e3addbaaa8314bd9a67bc0846f 100644
--- a/AKModel/locale/de_DE/LC_MESSAGES/django.po
+++ b/AKModel/locale/de_DE/LC_MESSAGES/django.po
@@ -1066,6 +1066,73 @@ msgstr "Konflikte"
 msgid "Prerequisites"
 msgstr "Voraussetzungen"
 
+#: AKModel/availability/models.py:89 AKModel/models.py:1358
+#: AKModel/models.py:1435
+msgid "Participant"
+msgstr "Teilnehmer*in"
+
+#: AKModel/availability/models.py:90
+msgid "Participant whose availability this is"
+msgstr "Teilnehmer*in, deren Verfügbarkeit hier abgebildet wird"
+
+#: AKModel/models.py:1359
+msgid "Participants"
+msgstr "Teilnehmende"
+
+#: AKModel/models.py:1363
+msgid ""
+"Name to identify a participant by (in case of questions from the organizers)"
+msgstr "Name, zur Identifikation bei Rückfragen von den Organisator*innen"
+
+#: AKModel/models.py:1370
+msgid "Participant's Requirements"
+msgstr "Anforderungen der Teilnehmer*in"
+
+#: AKModel/models.py:1370
+#, python-brace-format
+msgid "Anonymous {pk}"
+msgstr "Anonym {pk}"
+
+#: AKModel/models.py:1428
+msgid "AK Preference"
+msgstr "AK Präferenz"
+
+#: AKModel/models.py:1429
+msgid "AK Preferences"
+msgstr "AK Präferenzen"
+
+#: AKModel/models.py:1436
+msgid "Participant this preference belongs to"
+msgstr "Teilnehmer*in, zu der die Präferenz gehört"
+
+#: AKModel/models.py:1439
+msgid "AK Slot this preference belongs to"
+msgstr "AK-Slot zu dem die Präferenz gehört"
+
+#: AKModel/models.py:1445
+msgid "Ignore"
+msgstr "Ignorieren"
+
+#: AKModel/models.py:1446
+msgid "Prefer"
+msgstr "Präferenz"
+
+#: AKModel/models.py:1447
+msgid "Strong prefer"
+msgstr "Große Präferenz"
+
+#: AKModel/models.py:1448
+msgid "Required"
+msgstr "Erforderlich"
+
+#: AKModel/models.py:1450
+msgid "Preference"
+msgstr "Präferenz"
+
+#: AKModel/models.py:1451
+msgid "Preference level for the AK"
+msgstr "Präferenz-Level für den AK"
+
 #: AKModel/site.py:13 AKModel/site.py:14
 msgid "Administration"
 msgstr "Verwaltung"
diff --git a/AKModel/migrations/0065_eventparticipant_akpreference_and_more.py b/AKModel/migrations/0065_eventparticipant_akpreference_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..e169b484d675e0c3f94756e15794f4776700836c
--- /dev/null
+++ b/AKModel/migrations/0065_eventparticipant_akpreference_and_more.py
@@ -0,0 +1,132 @@
+# Generated by Django 4.2.13 on 2025-02-10 10:23
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("AKModel", "0064_event_export_slot"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="EventParticipant",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "name",
+                    models.CharField(
+                        blank=True,
+                        help_text="Name to identify a participant by (in case of questions from the organizers)",
+                        max_length=64,
+                        verbose_name="Nickname",
+                    ),
+                ),
+                (
+                    "institution",
+                    models.CharField(
+                        blank=True,
+                        help_text="Uni etc.",
+                        max_length=128,
+                        verbose_name="Institution",
+                    ),
+                ),
+                (
+                    "event",
+                    models.ForeignKey(
+                        help_text="Associated event",
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="AKModel.event",
+                        verbose_name="Event",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "Participant",
+                "verbose_name_plural": "Participants",
+                "ordering": ["name"],
+            },
+        ),
+        migrations.CreateModel(
+            name="AKPreference",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "preference",
+                    models.PositiveSmallIntegerField(
+                        choices=[
+                            (0, "Ignore"),
+                            (1, "Prefer"),
+                            (2, "Strong prefer"),
+                            (3, "Required"),
+                        ],
+                        default=0,
+                        help_text="Preference level for the AK",
+                        verbose_name="Preference",
+                    ),
+                ),
+                (
+                    "ak",
+                    models.ForeignKey(
+                        help_text="AK this preference belongs to",
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="AKModel.ak",
+                        verbose_name="AK",
+                    ),
+                ),
+                (
+                    "event",
+                    models.ForeignKey(
+                        help_text="Associated event",
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="AKModel.event",
+                        verbose_name="Event",
+                    ),
+                ),
+                (
+                    "participant",
+                    models.ForeignKey(
+                        help_text="Participant this preference belongs to",
+                        on_delete=django.db.models.deletion.CASCADE,
+                        to="AKModel.eventparticipant",
+                        verbose_name="Participant",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "AK Preference",
+                "verbose_name_plural": "AK Preferences",
+            },
+        ),
+        migrations.AddField(
+            model_name="availability",
+            name="participant",
+            field=models.ForeignKey(
+                blank=True,
+                help_text="Participant whose availability this is",
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="availabilities",
+                to="AKModel.eventparticipant",
+                verbose_name="Participant",
+            ),
+        ),
+    ]
diff --git a/AKModel/migrations/0066_akpreference_slot_alter_akpreference_unique_together_and_more.py b/AKModel/migrations/0066_akpreference_slot_alter_akpreference_unique_together_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..29f94ea84c6b32c7f5f231574c4cfc68447bed72
--- /dev/null
+++ b/AKModel/migrations/0066_akpreference_slot_alter_akpreference_unique_together_and_more.py
@@ -0,0 +1,34 @@
+# Generated by Django 4.2.13 on 2025-02-10 22:31
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("AKModel", "0065_eventparticipant_akpreference_and_more"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="akpreference",
+            name="slot",
+            field=models.ForeignKey(
+                default=None,
+                help_text="AKSlot this preference belongs to",
+                on_delete=django.db.models.deletion.CASCADE,
+                to="AKModel.akslot",
+                verbose_name="AKSlot",
+            ),
+            preserve_default=False,
+        ),
+        migrations.AlterUniqueTogether(
+            name="akpreference",
+            unique_together={("event", "participant", "slot")},
+        ),
+        migrations.RemoveField(
+            model_name="akpreference",
+            name="ak",
+        ),
+    ]
diff --git a/AKModel/migrations/0067_eventparticipant_requirements_and_more.py b/AKModel/migrations/0067_eventparticipant_requirements_and_more.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f1bd8e211af2c60a8d539c63f9011ab4c5dbf3c
--- /dev/null
+++ b/AKModel/migrations/0067_eventparticipant_requirements_and_more.py
@@ -0,0 +1,37 @@
+# Generated by Django 4.2.13 on 2025-02-11 00:23
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        (
+            "AKModel",
+            "0066_akpreference_slot_alter_akpreference_unique_together_and_more",
+        ),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="eventparticipant",
+            name="requirements",
+            field=models.ManyToManyField(
+                blank=True,
+                help_text="Participant's Requirements",
+                to="AKModel.akrequirement",
+                verbose_name="Requirements",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="akpreference",
+            name="slot",
+            field=models.ForeignKey(
+                help_text="AK Slot this preference belongs to",
+                on_delete=django.db.models.deletion.CASCADE,
+                to="AKModel.akslot",
+                verbose_name="AK Slot",
+            ),
+        ),
+    ]
diff --git a/AKModel/models.py b/AKModel/models.py
index 3b560e3f7378969f6d9171338ec85013da27c9c1..02160ec4a5024a2a76844ef30d63c80446f82b00 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -559,6 +559,8 @@ class Event(models.Model):
 
         rooms = Room.objects.filter(event=self).order_by()
         slots = AKSlot.objects.filter(event=self).order_by()
+        participants = EventParticipant.objects.filter(event=self).order_by()
+        owners = AKOwner.objects.filter(event=self).order_by().all()
 
         ak_availabilities = {
             ak.pk: Availability.union(ak.availabilities.all())
@@ -570,7 +572,11 @@ class Event(models.Model):
         }
         person_availabilities = {
             person.pk: Availability.union(person.availabilities.all())
-            for person in AKOwner.objects.filter(event=self)
+            for person in owners
+        }
+        participant_availabilities = {
+            participant.pk: Availability.union(participant.availabilities.all())
+            for participant in EventParticipant.objects.filter(event=self)
         }
 
         blocks = list(self.discretize_timeslots())
@@ -619,6 +625,11 @@ class Event(models.Model):
                     _generate_time_constraints("room", room_availabilities, timeslot.avail)
                 )
 
+                # add fulfilled time constraints for all participants that are not available for full event
+                time_constraints.extend(
+                    _generate_time_constraints("participant", participant_availabilities, timeslot.avail)
+                )
+
                 # add fulfilled time constraints for all AKSlots fixed to happen during timeslot
                 time_constraints.extend([
                     f"fixed-akslot-{slot.id}"
@@ -628,6 +639,7 @@ class Event(models.Model):
 
                 time_constraints.extend(timeslot.constraints)
                 time_constraints.extend(block_timeconstraints)
+                time_constraints.sort()
 
                 current_block.append({
                     "id": timeslot.idx,
@@ -635,7 +647,7 @@ class Event(models.Model):
                         "start": timeslot.avail.start.astimezone(self.timezone).strftime("%Y-%m-%d %H:%M"),
                         "end": timeslot.avail.end.astimezone(self.timezone).strftime("%Y-%m-%d %H:%M"),
                     },
-                    "fulfilled_time_constraints": sorted(time_constraints),
+                    "fulfilled_time_constraints": time_constraints,
                     })
 
             timeslots["blocks"].append(current_block)
@@ -651,14 +663,37 @@ class Event(models.Model):
             if hasattr(self, attr) and getattr(self, attr):
                 info_dict[attr] = getattr(self, attr)
 
-        return {
-            "participants": [],
+        data = {
+            "participants": [p.as_json_dict() for p in participants],
             "rooms": [r.as_json_dict() for r in rooms],
             "timeslots": timeslots,
             "info": info_dict,
             "aks": [ak.as_json_dict() for ak in slots],
         }
 
+        if EventParticipant.objects.exists():
+            next_participant_pk = EventParticipant.objects.latest("pk").pk + 1
+        else:
+            next_participant_pk = 1
+        # add one dummy participant per owner
+        # this ensures that the hard constraints from each owner are considered
+        for new_pk, owner in enumerate(owners, next_participant_pk):
+            owned_slots = slots.filter(ak__owners=owner).order_by().all()
+            if not owned_slots:
+                continue
+            new_participant_data = {
+                "id": new_pk,
+                "info": {"name": f"{owner} [AKOwner]"},
+                "room_constraints": [],
+                "time_constraints": [],
+                "preferences": [
+                    {"ak_id": slot.pk, "required": True, "preference_score": -1}
+                    for slot in owned_slots
+                ]
+            }
+            data["participants"].append(new_participant_data)
+        return data
+
 
 class AKOwner(models.Model):
     """ An AKOwner describes the person organizing/holding an AK.
@@ -1091,8 +1126,7 @@ class Room(models.Model):
                 "name": self.name,
             },
             "capacity": self.capacity,
-            "fulfilled_room_constraints": [constraint.name
-                                           for constraint in self.properties.all()],
+            "fulfilled_room_constraints": list(self.properties.values_list("name", flat=True)),
             "time_constraints": time_constraints
         }
 
@@ -1238,20 +1272,14 @@ class AKSlot(models.Model):
             "id": self.pk,
             "duration": math.ceil(self.duration / self.event.export_slot - ceil_offet_eps),
             "properties": {
-                "conflicts":
-                    sorted(
-                        [conflict.pk for conflict in conflict_slots.all()]
-                      + [second_slot.pk for second_slot in other_ak_slots.all()]
-                    ),
-                "dependencies": sorted([dep.pk for dep in dependency_slots.all()]),
+                "conflicts": list((conflict_slots | other_ak_slots).values_list("pk", flat=True).order_by()),
+                "dependencies": list(dependency_slots.values_list("pk", flat=True).order_by()),
             },
-            "room_constraints": [constraint.name
-                                 for constraint in self.ak.requirements.all()],
+            "room_constraints": list(self.ak.requirements.values_list("name", flat=True).order_by()),
             "time_constraints": ["resolution"] if self.ak.reso else [],
             "info": {
                 "name": self.ak.name,
-                "head": ", ".join([str(owner)
-                                   for owner in self.ak.owners.all()]),
+                "head": ", ".join([str(owner) for owner in self.ak.owners.order_by().all()]),
                 "description": self.ak.description,
                 "reso": self.ak.reso,
                 "duration_in_hours": float(self.duration),
@@ -1575,3 +1603,126 @@ class DefaultSlot(models.Model):
 
     def __str__(self):
         return f"{self.event}: {self.start_simplified} - {self.end_simplified}"
+
+
+class EventParticipant(models.Model):
+    """ A participant describes a person taking part in an event."""
+
+    class Meta:
+        verbose_name = _('Participant')
+        verbose_name_plural = _('Participants')
+        ordering = ['name']
+
+    name = models.CharField(max_length=64, blank=True, verbose_name=_('Nickname'),
+                            help_text=_('Name to identify a participant by (in case of questions from the organizers)'))
+    institution = models.CharField(max_length=128, blank=True, verbose_name=_('Institution'), help_text=_('Uni etc.'))
+
+    event = models.ForeignKey(to=Event, on_delete=models.CASCADE, verbose_name=_('Event'),
+                              help_text=_('Associated event'))
+
+    requirements = models.ManyToManyField(to=AKRequirement, blank=True, verbose_name=_('Requirements'),
+                                          help_text=_("Participant's Requirements"))
+
+    def __str__(self) -> str:
+        string = _("Anonymous {pk}").format(pk=self.pk) if not self.name else self.name
+        if self.institution:
+            string += f" ({self.institution})"
+        return string
+
+    @property
+    def availabilities(self):
+        """
+        Get all availabilities associated to this EventParticipant
+        :return: availabilities
+        :rtype: QuerySet[Availability]
+        """
+        return "Availability".objects.filter(participant=self)
+
+    def as_json_dict(self) -> dict[str, Any]:
+        """Return a json representation of this participant object.
+
+        :return: The json dict representation is constructed
+            following the input specification of the KoMa conference optimizer, cf.
+            https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format
+        :rtype: dict[str, Any]
+        """
+        # local import to prevent cyclic import
+        # pylint: disable=import-outside-toplevel
+        from AKModel.availability.models import Availability
+
+        data = {
+            "id": self.pk,
+            "info": {"name": str(self)},
+            "room_constraints": list(self.requirements.values_list("name", flat=True).order_by()),
+            "time_constraints": [],
+        }
+        data["preferences"] = [
+            pref.as_json_dict()
+            for pref in AKPreference.objects.filter(
+                participant=self, preference__gt=0
+            ).select_related("slot").order_by()
+        ]
+
+        avails = self.availabilities.all()
+        if avails and not Availability.is_event_covered(self.event, avails):
+            # participant has restricted availability
+            if AKPreference.objects.filter(
+                event=self.event,
+                participant=self,
+                preference=AKPreference.PreferenceLevel.REQUIRED,
+            ):
+                # partipant is actually required for AKs
+                data["time_constraints"].append(f"availability-participant-{self.pk}")
+
+        data["time_constraints"].sort()
+        return data
+
+
+class AKPreference(models.Model):
+    """Model representing the preference of a participant to an AK."""
+
+    class Meta:
+        verbose_name = _('AK Preference')
+        verbose_name_plural = _('AK Preferences')
+        unique_together = [['event', 'participant', 'slot']]
+
+    event = models.ForeignKey(to=Event, on_delete=models.CASCADE, verbose_name=_('Event'),
+                              help_text=_('Associated event'))
+
+    participant = models.ForeignKey(to=EventParticipant, on_delete=models.CASCADE, verbose_name=_('Participant'),
+                              help_text=_('Participant this preference belongs to'))
+
+    slot = models.ForeignKey(to=AKSlot, on_delete=models.CASCADE, verbose_name=_('AK Slot'),
+                           help_text=_('AK Slot this preference belongs to'))
+
+    class PreferenceLevel(models.IntegerChoices):
+        """
+        Possible preference values
+        """
+        IGNORE = 0, _('Ignore')
+        PREFER = 1, _('Prefer')
+        STRONG_PREFER = 2, _("Strong prefer")
+        REQUIRED = 3, _("Required")
+
+    preference = models.PositiveSmallIntegerField(verbose_name=_('Preference'), choices=PreferenceLevel.choices,
+                                             help_text=_('Preference level for the AK'),
+                                             blank=False,
+                                             default=PreferenceLevel.IGNORE)
+
+    def __str__(self) -> str:
+        return "AKPreference: " + json.dumps(self.as_json_dict())
+
+    def as_json_dict(self) -> dict[str, int | bool]:
+        """Return a json representation of this AKPreference object.
+
+        :return: The json dict representation is constructed
+            following the input specification of the KoMa conference optimizer, cf.
+            https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format
+        :rtype: dict[str, Any]
+        """
+        preference_score = self.preference if self.preference != self.PreferenceLevel.REQUIRED else -1
+        return {
+            "ak_id": self.slot.pk,
+            "required": self.preference == self.PreferenceLevel.REQUIRED,
+            "preference_score": preference_score
+        }
diff --git a/AKModel/tests/test_json_export.py b/AKModel/tests/test_json_export.py
index f0cbba1c8c655a30c4329b9d70d726cad4c7e0d3..50482ce10c010b7d05fb9d8a3af529e63cfdfd88 100644
--- a/AKModel/tests/test_json_export.py
+++ b/AKModel/tests/test_json_export.py
@@ -12,7 +12,17 @@ from django.urls import reverse
 from jsonschema.exceptions import best_match
 
 from AKModel.availability.models import Availability
-from AKModel.models import AK, AKCategory, AKOwner, AKSlot, DefaultSlot, Event, Room
+from AKModel.models import (
+    AK,
+    AKCategory,
+    AKOwner,
+    AKPreference,
+    AKSlot,
+    DefaultSlot,
+    Event,
+    EventParticipant,
+    Room,
+)
 from AKModel.utils import construct_schema_validator
 
 
@@ -52,7 +62,10 @@ class JSONExportTest(TestCase):
 
         self.ak_slots: Iterable[AKSlot] = []
         self.rooms: Iterable[Room] = []
+        self.participants: Iterable[EventParticipant] = []
+        self.owners: Iterable[AKOwner] = []
         self.slots_in_an_hour: float = 1.0
+        self.max_participant_pk = 0
         self.event: Event | None = None
 
     def set_up_event(self, event: Event) -> None:
@@ -83,6 +96,15 @@ class JSONExportTest(TestCase):
             .all()
         )
         self.rooms = Room.objects.filter(event__slug=event.slug).all()
+        self.participants = EventParticipant.objects.filter(
+            event__slug=event.slug
+        ).all()
+        self.owners = AKOwner.objects.filter(event__slug=event.slug).all()
+
+        self.max_participant_pk = (
+            self.participants.latest("pk").pk if self.participants else 0
+        )
+
         self.slots_in_an_hour = 1 / self.export_dict["timeslots"]["info"]["duration"]
         self.event = event
 
@@ -167,12 +189,6 @@ class JSONExportTest(TestCase):
             with self.subTest(event=event):
                 self.set_up_event(event=event)
 
-                self.assertEqual(
-                    self.export_dict["participants"],
-                    [],
-                    "Empty participant list expected",
-                )
-
                 info_keys = {"title": "name", "slug": "slug"}
                 for attr in ["contact_email", "place"]:
                     if hasattr(self.event, attr) and getattr(self.event, attr):
@@ -611,7 +627,7 @@ class JSONExportTest(TestCase):
                         # add owner constraints
                         fulfilled_time_constraints |= {
                             f"availability-person-{owner.id}"
-                            for owner in AKOwner.objects.filter(event=self.event).all()
+                            for owner in self.owners
                             if self._is_restricted_and_contained_slot(
                                 timeslot_avail,
                                 Availability.union(owner.availabilities.all()),
@@ -628,6 +644,16 @@ class JSONExportTest(TestCase):
                             )
                         }
 
+                        # add participant constraints
+                        fulfilled_time_constraints |= {
+                            f"availability-participant-{participant.id}"
+                            for participant in self.participants
+                            if self._is_restricted_and_contained_slot(
+                                timeslot_avail,
+                                Availability.union(participant.availabilities.all()),
+                            )
+                        }
+
                         # add ak constraints
                         fulfilled_time_constraints |= {
                             f"availability-ak-{ak.id}"
@@ -692,3 +718,153 @@ class JSONExportTest(TestCase):
                 self.assertEqual(
                     block_names, self.export_dict["timeslots"]["info"]["blocknames"]
                 )
+
+    def _owner_has_ak(self, owner: AKOwner) -> bool:
+        owned_aks = self.ak_slots.filter(ak__owners=owner).all()
+        return bool(owned_aks)
+
+    def test_all_participants_exported(self):
+        """Test if exported Rooms match the rooms of Event."""
+        for event in Event.objects.all():
+            with self.subTest(event=event):
+                self.set_up_event(event=event)
+
+                participant_ids = set(self.participants.values_list("pk", flat=True))
+                for idx, owner in enumerate(self.owners, self.max_participant_pk + 1):
+                    if self._owner_has_ak(owner):
+                        participant_ids.add(idx)
+
+                self.assertEqual(
+                    participant_ids,
+                    self.export_objects["participants"].keys(),
+                    "Exported Participants does not match the Participants of the event",
+                )
+
+    def test_participant_info(self):
+        """Test if contents of participants info dict is correct."""
+        for event in Event.objects.all():
+            with self.subTest(event=event):
+                self.set_up_event(event=event)
+
+                for participant in self.participants:
+                    export_participant = self.export_objects["participants"][
+                        participant.pk
+                    ]
+                    self.assertEqual(
+                        str(participant), export_participant["info"]["name"]
+                    )
+
+                for idx, owner in enumerate(self.owners, self.max_participant_pk + 1):
+                    if not self._owner_has_ak(owner):
+                        continue
+                    export_participant = self.export_objects["participants"][idx]
+                    self.assertEqual(
+                        str(owner) + " [AKOwner]", export_participant["info"]["name"]
+                    )
+
+    def test_participant_timeconstraints(self):
+        """Test if participant time constraints are exported as expected."""
+        for event in Event.objects.all():
+            with self.subTest(event=event):
+                self.set_up_event(event=event)
+                for participant in self.participants:
+                    export_participant = self.export_objects["participants"][
+                        participant.pk
+                    ]
+
+                    time_constraints = set()
+                    participant_avails = participant.availabilities.all()
+                    if participant_avails and not Availability.is_event_covered(
+                        self.event, participant_avails
+                    ):
+                        # participant has restricted availability
+                        if AKPreference.objects.filter(
+                            event=self.event,
+                            participant=participant,
+                            preference=AKPreference.PreferenceLevel.REQUIRED,
+                        ):
+                            # partipant is actually required for AKs
+                            time_constraints.add(
+                                f"availability-participant-{participant.pk}"
+                            )
+
+                    self.assertEqual(
+                        set(export_participant["time_constraints"]), time_constraints
+                    )
+
+                # dummy participants have no time constraints
+                for idx, owner in enumerate(self.owners, self.max_participant_pk + 1):
+                    if not self._owner_has_ak(owner):
+                        continue
+                    export_participant = self.export_objects["participants"][idx]
+                    self.assertEqual(export_participant["time_constraints"], [])
+
+    def test_participant_roomconstraints(self):
+        """Test if participant room constraints are exported as expected."""
+        for event in Event.objects.all():
+            with self.subTest(event=event):
+                self.set_up_event(event=event)
+                for participant in self.participants:
+                    export_participant = self.export_objects["participants"][
+                        participant.pk
+                    ]
+                    room_constraints = [
+                        constr.name for constr in participant.requirements.all()
+                    ]
+                    self.assertCountEqual(
+                        export_participant["room_constraints"], room_constraints
+                    )
+
+                for idx, owner in enumerate(self.owners, self.max_participant_pk + 1):
+                    if not self._owner_has_ak(owner):
+                        continue
+                    export_participant = self.export_objects["participants"][idx]
+                    self.assertEqual(export_participant["room_constraints"], [])
+
+    def test_preferences(self):
+        """Test if preferences are exported as expected."""
+
+        def _preference_json(pref: AKPreference):
+            return {
+                "ak_id": pref.slot.pk,
+                "required": pref.preference == AKPreference.PreferenceLevel.REQUIRED,
+                "preference_score": (
+                    pref.preference
+                    if pref.preference != AKPreference.PreferenceLevel.REQUIRED
+                    else -1
+                ),
+            }
+
+        for event in Event.objects.all():
+            with self.subTest(event=event):
+                self.set_up_event(event=event)
+                for participant in self.participants:
+                    export_participant = self.export_objects["participants"][
+                        participant.pk
+                    ]
+                    preferences = [
+                        _preference_json(pref)
+                        for pref in AKPreference.objects.filter(
+                            participant=participant, preference__gt=0
+                        ).select_related("slot")
+                    ]
+                    self.assertCountEqual(
+                        export_participant["preferences"], preferences
+                    )
+
+                for idx, owner in enumerate(self.owners, self.max_participant_pk + 1):
+                    owned_slots = self.ak_slots.filter(ak__owners=owner).all()
+                    if not owned_slots:
+                        continue
+                    preferences = [
+                        {
+                            "ak_id": slot.pk,
+                            "required": True,
+                            "preference_score": -1,
+                        }
+                        for slot in owned_slots
+                    ]
+                    export_participant = self.export_objects["participants"][idx]
+                    self.assertCountEqual(
+                        export_participant["preferences"], preferences
+                    )
diff --git a/AKModel/tests/test_views.py b/AKModel/tests/test_views.py
index 639847458a866c62304164bc64b08570b8d151d5..eb9c842da13c548984f3ee8c9591849f06fe8144 100644
--- a/AKModel/tests/test_views.py
+++ b/AKModel/tests/test_views.py
@@ -5,20 +5,20 @@ from django.contrib.auth import get_user_model
 from django.contrib.messages import get_messages
 from django.contrib.messages.storage.base import Message
 from django.test import TestCase
-from django.urls import reverse_lazy, reverse
+from django.urls import reverse, reverse_lazy
 
 from AKModel.models import (
-    Event,
-    AKOwner,
+    AK,
     AKCategory,
-    AKTrack,
+    AKOrgaMessage,
+    AKOwner,
     AKRequirement,
-    AK,
-    Room,
     AKSlot,
-    AKOrgaMessage,
+    AKTrack,
     ConstraintViolation,
     DefaultSlot,
+    Event,
+    Room,
 )