Source code for django_program.registration.attendee

"""Attendee profile models for conference registration.

Provides an abstract base for projects that need custom attendee profile fields,
and a concrete ``Attendee`` model that links users to conferences with check-in
tracking and access codes.
"""

import secrets
import string

from django.conf import settings
from django.db import models


[docs] def generate_access_code(*, max_retries: int = 10) -> str: """Generate an 8-character uppercase alphanumeric access code. Retries on collision up to ``max_retries`` times. The keyspace is 36^8 (~2.8 trillion), so collisions are extremely unlikely. Args: max_retries: Maximum retry attempts on collision. Returns: A unique random string of 8 uppercase letters and digits. Raises: RuntimeError: If a unique code cannot be generated after retries. """ from django_program.registration.attendee import Attendee # noqa: PLC0415 alphabet = string.ascii_uppercase + string.digits for _ in range(max_retries): code = "".join(secrets.choice(alphabet) for _ in range(8)) if not Attendee.objects.filter(access_code=code).exists(): return code msg = f"Failed to generate unique access code after {max_retries} attempts" raise RuntimeError(msg)
[docs] class AttendeeProfileBase(models.Model): """Abstract base for attendee profiles. Projects extend this with custom fields (e.g. dietary restrictions, t-shirt size) by subclassing and pointing ``DJANGO_PROGRAM.attendee_profile_model`` at the concrete subclass. """ user = models.OneToOneField( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="%(class)s", ) access_code = models.CharField(max_length=20, unique=True, editable=False) completed_registration = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)
[docs] class Meta: abstract = True
[docs] def save(self, *args: object, **kwargs: object) -> None: # pragma: no cover — abstract base """Auto-generate access code on first save if not already set.""" if not self.access_code: self.access_code = generate_access_code() super().save(*args, **kwargs)
[docs] class Attendee(models.Model): """Links a user to a conference with registration state and check-in tracking. Unlike ``AttendeeProfileBase``, this uses a ``ForeignKey`` to the user because a single user can attend multiple conferences. The ``order`` field is also a ``ForeignKey`` (not OneToOne) so that replacement/upgrade orders can update the link without constraint violations. """ user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="attendees", ) conference = models.ForeignKey( "program_conference.Conference", on_delete=models.CASCADE, related_name="attendees", ) order = models.ForeignKey( "program_registration.Order", on_delete=models.SET_NULL, null=True, blank=True, related_name="attendees", ) access_code = models.CharField(max_length=20, unique=True, editable=False) checked_in_at = models.DateTimeField(null=True, blank=True) completed_registration = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: unique_together = [("user", "conference")] def __str__(self) -> str: return f"{self.user} @ {self.conference}"
[docs] def save(self, *args: object, **kwargs: object) -> None: """Auto-generate access code on first save if not already set.""" if not self.access_code: self.access_code = generate_access_code() super().save(*args, **kwargs)