diff --git a/src/core/migrations/0165_alter_eventparticipant_options_and_more.py b/src/core/migrations/0165_alter_eventparticipant_options_and_more.py new file mode 100644 index 0000000000000000000000000000000000000000..39e98e039cd7c719039c01f09bbab21689605bfd --- /dev/null +++ b/src/core/migrations/0165_alter_eventparticipant_options_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.3 on 2024-12-15 17:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0164_alter_translation_keys"), + ] + + operations = [ + migrations.AlterModelOptions( + name="eventparticipant", + options={"ordering": ["order"]}, + ), + migrations.AddField( + model_name="eventparticipant", + name="order", + field=models.PositiveIntegerField(default=0), + ), + ] diff --git a/src/core/models/events.py b/src/core/models/events.py index d2dfb2f09fddac209126ded5a44ca1b94f9e15f8..c05859c5818348c64210734f4037a2a62c5651b5 100644 --- a/src/core/models/events.py +++ b/src/core/models/events.py @@ -425,10 +425,10 @@ class Event(TaggedItemMixin, BackendMixin, ActivityLogMixin, models.Model): if 'speakers' in data: if speaker_lookup is not None: obj.save() # TODO: check if we can do this better, but we need the model saved for adding speakers - for item in data['speakers']: + for order, item in enumerate(data['speakers']): speaker = speaker_lookup(item) if speaker is not None: - obj.ensure_speaker(speaker) + obj.ensure_speaker(speaker, order) else: raise RuntimeWarning('Event.from_dict() got data with "speakers" but no speaker_lookup was provided.') if pop_used_keys: @@ -632,7 +632,7 @@ class Event(TaggedItemMixin, BackendMixin, ActivityLogMixin, models.Model): # we require a configured BigBlueButton integration return settings.INTEGRATIONS_BBB - def ensure_speaker(self, user: PlatformUser): + def ensure_speaker(self, user: PlatformUser, order: int): entry, created = self.participants.get_or_create(participant=user) if created: logger.info( @@ -646,6 +646,7 @@ class Event(TaggedItemMixin, BackendMixin, ActivityLogMixin, models.Model): entry.role = EventParticipant.Role.SPEAKER entry.is_public = True entry.is_accepted = True + entry.order = order entry.save() logger.info( 'Forced participant #%s (%s) of event %s to accepted+public+speaker.', @@ -695,6 +696,9 @@ class EventAttachment(models.Model): class EventParticipant(models.Model): + class Meta: + ordering = ['order'] + class Role(models.TextChoices): SPEAKER = 'speaker', _('EventParticipant__type-speaker') ANGEL = 'angel', _('EventParticipant__type-angel') @@ -710,6 +714,9 @@ class EventParticipant(models.Model): max_length=20, choices=Role.choices, default=Role.REGULAR, help_text=_('EventParticipant__role__help'), verbose_name=_('EventParticipant__role') ) + # Future TODO: evaluate https://pypi.org/project/django-sortedm2m/ but for now we just use a simple order field + order = models.PositiveIntegerField(default=0) + is_accepted = models.BooleanField(default=False, help_text=_('EventParticipant__is_accepted__help'), verbose_name=_('EventParticipant__is_accepted')) is_public = models.BooleanField(default=False, help_text=_('EventParticipant__is_public__help'), verbose_name=_('EventParticipant__is_public')) @@ -727,6 +734,10 @@ class EventParticipant(models.Model): except ConferenceMember.DoesNotExist: raise ValidationError(_('EventParticipant__must_be_conference_member')) + if not self.order: # if 'order' is not explicitly set + last_order = EventParticipant.objects.filter(event=self.event).aggregate(models.Max('order')).get('order__max') + self.order = (last_order or 0) + 1 + def __str__(self): result = self.participant.username if self.role in [self.Role.SPEAKER, self.Role.ANGEL]: