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:
@@ -1,5 +1,6 @@
|
||||
from django.db import models, transaction
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
from django.utils.text import slugify
|
||||
|
||||
@@ -14,6 +15,7 @@ from core.media import (
|
||||
safe_process_public_image,
|
||||
)
|
||||
from core.models import BaseModel
|
||||
from apps.events.event_status import EVENT_STATUS_LABELS
|
||||
|
||||
|
||||
class Event(BaseModel):
|
||||
@@ -22,12 +24,6 @@ class Event(BaseModel):
|
||||
ON_SITE = 'on_site', 'حضوری'
|
||||
HYBRID = 'hybrid', 'آنلاین/حضوری'
|
||||
|
||||
class StatusChoices(models.TextChoices):
|
||||
DRAFT = 'draft', 'Draft'
|
||||
PUBLISHED = 'published', 'Published'
|
||||
CANCELLED = 'cancelled', 'Cancelled'
|
||||
COMPLETED = 'completed', 'Completed'
|
||||
|
||||
title = models.CharField(max_length=255)
|
||||
slug = models.SlugField(max_length=255, unique=True, blank=True)
|
||||
description = models.TextField(help_text="Event description in Markdown format")
|
||||
@@ -43,7 +39,8 @@ class Event(BaseModel):
|
||||
online_link = models.URLField(max_length=500, blank=True, null=True,
|
||||
help_text="Link for online events (e.g., Zoom, Google Meet)")
|
||||
|
||||
status = models.CharField(max_length=10, choices=StatusChoices.choices, default=StatusChoices.DRAFT)
|
||||
is_published = models.BooleanField(default=False)
|
||||
is_cancelled = models.BooleanField(default=False)
|
||||
capacity = models.PositiveIntegerField(null=True, blank=True,
|
||||
help_text="Maximum number of attendees (leave blank for unlimited)")
|
||||
|
||||
@@ -63,14 +60,75 @@ class Event(BaseModel):
|
||||
class Meta:
|
||||
ordering = ['-start_time']
|
||||
indexes = [
|
||||
models.Index(fields=['status', 'start_time']),
|
||||
models.Index(fields=['is_published', 'start_time'], name='event_pub_start_idx'),
|
||||
models.Index(fields=['event_type']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
should_validate_deadline = self.pk is None
|
||||
if self.pk is not None:
|
||||
previous_schedule = type(self).all_objects.filter(pk=self.pk).values(
|
||||
"registration_end_date", "start_time"
|
||||
).first()
|
||||
should_validate_deadline = previous_schedule is None or (
|
||||
previous_schedule["registration_end_date"] != self.registration_end_date
|
||||
or previous_schedule["start_time"] != self.start_time
|
||||
)
|
||||
if (
|
||||
should_validate_deadline
|
||||
and self.registration_end_date
|
||||
and self.start_time
|
||||
and self.registration_end_date > self.start_time
|
||||
):
|
||||
raise ValidationError({
|
||||
"registration_end_date": "مهلت ثبتنام باید تا زمان شروع رویداد پایان یابد."
|
||||
})
|
||||
|
||||
@property
|
||||
def effective_registration_end_date(self):
|
||||
"""Cap registration at event start, including records with legacy dates."""
|
||||
if self.registration_end_date is None:
|
||||
return self.start_time
|
||||
return min(self.registration_end_date, self.start_time)
|
||||
|
||||
def get_display_status(self, *, now=None, registration_count=None):
|
||||
now = now or timezone.now()
|
||||
if not self.is_published:
|
||||
return "draft"
|
||||
if self.is_cancelled:
|
||||
return "cancelled"
|
||||
if self.end_time <= now:
|
||||
return "completed"
|
||||
if self.start_time <= now:
|
||||
return "in_progress"
|
||||
if now >= self.effective_registration_end_date:
|
||||
return "registration_closed"
|
||||
|
||||
if registration_count is None:
|
||||
registration_count = getattr(self, "browse_registration_count", None)
|
||||
if registration_count is None:
|
||||
registration_count = self.current_attendees_count
|
||||
if self.capacity is not None and registration_count >= self.capacity:
|
||||
return "full"
|
||||
if self.registration_start_date is not None and now < self.registration_start_date:
|
||||
return "upcoming"
|
||||
return "registration_open"
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""Computed lifecycle state; no status value is persisted on Event."""
|
||||
return self.get_display_status()
|
||||
|
||||
@property
|
||||
def status_label(self):
|
||||
return EVENT_STATUS_LABELS[self.status]
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
previous_image_name = get_image_previous_name(self, "featured_image")
|
||||
current_image_name = self.featured_image.name if self.featured_image else None
|
||||
|
||||
@@ -103,8 +161,13 @@ class Event(BaseModel):
|
||||
@property
|
||||
def is_registration_open(self):
|
||||
now = timezone.now()
|
||||
return (self.registration_start_date is None or now >= self.registration_start_date) and \
|
||||
(self.registration_end_date is None or now <= self.registration_end_date)
|
||||
return (
|
||||
self.is_published
|
||||
and not self.is_cancelled
|
||||
and now < self.start_time
|
||||
and (self.registration_start_date is None or now >= self.registration_start_date)
|
||||
and now < self.effective_registration_end_date
|
||||
)
|
||||
|
||||
@property
|
||||
def current_attendees_count(self):
|
||||
|
||||
Reference in New Issue
Block a user