89 lines
3.1 KiB
Python
89 lines
3.1 KiB
Python
from django.db import models
|
|
from django.template.defaultfilters import slugify
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
|
|
|
|
class Event(models.Model):
|
|
title = models.CharField(max_length=180)
|
|
slug = models.SlugField(unique=True, max_length=200, blank=True)
|
|
summary = models.CharField(max_length=260)
|
|
description = models.TextField()
|
|
venue = models.CharField(max_length=180)
|
|
start_at = models.DateTimeField()
|
|
end_at = models.DateTimeField()
|
|
capacity = models.PositiveIntegerField(blank=True, null=True)
|
|
is_published = models.BooleanField(default=True)
|
|
registration_opens = models.DateTimeField(blank=True, null=True)
|
|
registration_closes = models.DateTimeField(blank=True, null=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
ordering = ['start_at']
|
|
|
|
def __str__(self):
|
|
return self.title
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.slug:
|
|
base_slug = slugify(self.title)[:180] or 'event'
|
|
slug = base_slug
|
|
counter = 2
|
|
while Event.objects.exclude(pk=self.pk).filter(slug=slug).exists():
|
|
slug = f'{base_slug}-{counter}'[:200]
|
|
counter += 1
|
|
self.slug = slug
|
|
super().save(*args, **kwargs)
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('event_detail', args=[self.slug])
|
|
|
|
@property
|
|
def confirmed_registrations_count(self):
|
|
return self.registrations.filter(status=Registration.Status.CONFIRMED).count()
|
|
|
|
@property
|
|
def waitlist_count(self):
|
|
return self.registrations.filter(status=Registration.Status.WAITLIST).count()
|
|
|
|
@property
|
|
def spots_remaining(self):
|
|
if self.capacity is None:
|
|
return None
|
|
return max(self.capacity - self.confirmed_registrations_count, 0)
|
|
|
|
@property
|
|
def registration_is_open(self):
|
|
now = timezone.now()
|
|
if not self.is_published:
|
|
return False
|
|
if self.registration_opens and now < self.registration_opens:
|
|
return False
|
|
if self.registration_closes and now > self.registration_closes:
|
|
return False
|
|
return True
|
|
|
|
|
|
class Registration(models.Model):
|
|
class Status(models.TextChoices):
|
|
CONFIRMED = 'confirmed', 'Confirmed'
|
|
WAITLIST = 'waitlist', 'Waitlist'
|
|
|
|
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='registrations')
|
|
full_name = models.CharField(max_length=160)
|
|
email = models.EmailField()
|
|
company = models.CharField(max_length=160, blank=True)
|
|
notes = models.TextField(blank=True)
|
|
status = models.CharField(max_length=24, choices=Status.choices, default=Status.CONFIRMED)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
ordering = ['created_at']
|
|
constraints = [
|
|
models.UniqueConstraint(fields=['event', 'email'], name='unique_registration_per_event_email'),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f'{self.full_name} · {self.event.title}'
|