feat: derive event lifecycle status from dates and flags

Replace persisted event statuses with publication and cancellation flags, and compute public lifecycle labels from event timing and registration capacity. Keep filters, registration checks, and profile responses aligned with the derived status.
This commit is contained in:
2026-10-03 12:46:16 +03:30
parent f268c9b88a
commit 6c5fadef2f
26 changed files with 525 additions and 178 deletions

View File

@@ -14,11 +14,11 @@ from apps.events.checkout import cancel_event_checkouts
class EventAdmin(BaseModelAdmin, ImportExportModelAdmin):
resource_class = EventResource
list_display = (
'title', 'event_type', 'start_time_display', 'end_time_display', 'status',
'title', 'event_type', 'start_time_display', 'end_time_display', 'status_display',
'price_display', 'capacity_display', 'attendees_display', 'is_registration_open_display',
)
list_filter = (
'event_type', 'status', 'is_deleted', 'start_time', 'end_time',
'event_type', 'is_published', 'is_cancelled', 'is_deleted', 'start_time', 'end_time',
'registration_start_date', 'registration_end_date', SoftDeleteListFilter,
)
search_fields = ('title', 'description', 'address')
@@ -27,7 +27,8 @@ class EventAdmin(BaseModelAdmin, ImportExportModelAdmin):
filter_horizontal = ('gallery_images',)
fieldsets = (
('Event Details', {'fields': ('title', 'slug', 'description', 'featured_image')}),
('Timing & Type', {'fields': ('start_time', 'end_time', 'event_type', 'status')}),
('Timing & Type', {'fields': ('start_time', 'end_time', 'event_type')}),
('Publication', {'fields': ('is_published', 'is_cancelled')}),
('Location & Online', {
'fields': ('address', 'location', 'online_link'),
'description': 'For On-Site or Hybrid events, provide address and select on map. For Online events, provide a link.',
@@ -41,9 +42,13 @@ class EventAdmin(BaseModelAdmin, ImportExportModelAdmin):
)
readonly_fields = ('deleted_at',)
actions = BaseModelAdmin.actions + [
'make_published', 'make_draft', 'make_cancelled', 'make_completed', 'restore_events',
'make_published', 'make_draft', 'make_cancelled', 'restore_events',
]
@admin.display(description='Status')
def status_display(self, obj):
return obj.status_label
@admin.display(description='Price')
def price_display(self, obj):
return obj.price if obj.price is not None else 'رایگان'
@@ -70,30 +75,25 @@ class EventAdmin(BaseModelAdmin, ImportExportModelAdmin):
@admin.action(description='Mark selected events as published')
def make_published(self, request, queryset):
queryset.update(status=Event.StatusChoices.PUBLISHED)
self.message_user(request, f'Published {queryset.count()} events.')
count = queryset.update(is_published=True)
self.message_user(request, f'Published {count} events.')
@admin.action(description='Mark selected events as draft')
def make_draft(self, request, queryset):
queryset.update(status=Event.StatusChoices.DRAFT)
self.message_user(request, f'Marked {queryset.count()} events as draft.')
count = queryset.update(is_published=False)
self.message_user(request, f'Marked {count} events as draft.')
@admin.action(description='Mark selected events as cancelled')
def make_cancelled(self, request, queryset):
count = 0
for event in queryset:
if event.status != Event.StatusChoices.CANCELLED:
event.status = Event.StatusChoices.CANCELLED
event.save(update_fields=['status'])
if not event.is_cancelled:
event.is_cancelled = True
event.save(update_fields=['is_cancelled', 'updated_at'])
cancel_event_checkouts(event.pk)
count += 1
self.message_user(request, f'Cancelled {count} events.')
@admin.action(description='Mark selected events as completed')
def make_completed(self, request, queryset):
queryset.update(status=Event.StatusChoices.COMPLETED)
self.message_user(request, f'Completed {queryset.count()} events.')
@admin.action(description='Restore selected events')
def restore_events(self, request, queryset):
for event in queryset:
@@ -101,11 +101,11 @@ class EventAdmin(BaseModelAdmin, ImportExportModelAdmin):
self.message_user(request, f'Restored {queryset.count()} events.')
def save_model(self, request, obj, form, change):
previous_status = None
was_cancelled = None
if change and obj.pk:
previous_status = Event.all_objects.filter(pk=obj.pk).values_list("status", flat=True).first()
was_cancelled = Event.all_objects.filter(pk=obj.pk).values_list("is_cancelled", flat=True).first()
super().save_model(request, obj, form, change)
if previous_status != Event.StatusChoices.CANCELLED and obj.status == Event.StatusChoices.CANCELLED:
if was_cancelled is False and obj.is_cancelled:
cancel_event_checkouts(obj.pk)