"""Badge models for generating attendee badges with QR codes.
Provides a configurable badge template system and a model for tracking
generated badge files (PDF or PNG) per attendee.
"""
from django.core.validators import RegexValidator
from django.db import models
_hex_color_validator = RegexValidator(r"^#[0-9A-Fa-f]{6}$", "Enter a valid hex color (e.g. #FF0000).")
[docs]
class BadgeTemplate(models.Model):
"""Configurable badge layout template for a conference.
Defines dimensions, visible fields, color scheme, and optional logo
for generating attendee badges. Each conference can have multiple
templates but only one may be marked as the default.
"""
conference = models.ForeignKey(
"program_conference.Conference",
on_delete=models.CASCADE,
related_name="badge_templates",
)
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200)
is_default = models.BooleanField(default=False)
# Layout config
width_mm = models.PositiveIntegerField(
default=102,
help_text="Badge width in millimeters (default 102mm / 4 inches).",
)
height_mm = models.PositiveIntegerField(
default=152,
help_text="Badge height in millimeters (default 152mm / 6 inches, portrait).",
)
# Ticket type banner position
[docs]
class BannerPosition(models.TextChoices):
"""Where to place the ticket type banner on the badge."""
BELOW_HEADER = "below_header", "Below header"
ABOVE_NAME = "above_name", "Above name"
BELOW_NAME = "below_name", "Below name"
BOTTOM = "bottom", "Bottom of badge"
ticket_banner_position = models.CharField(
max_length=20,
choices=BannerPosition.choices,
default=BannerPosition.BELOW_HEADER,
help_text="Where to place the ticket type banner (Speaker, Sponsor, etc.).",
)
# What to show
show_name = models.BooleanField(default=True)
show_email = models.BooleanField(default=False)
show_company = models.BooleanField(default=False)
show_ticket_type = models.BooleanField(default=True)
show_qr_code = models.BooleanField(default=True)
show_conference_name = models.BooleanField(default=True)
# Styling
background_color = models.CharField(max_length=7, default="#FFFFFF", validators=[_hex_color_validator])
text_color = models.CharField(max_length=7, default="#000000", validators=[_hex_color_validator])
accent_color = models.CharField(max_length=7, default="#4338CA", validators=[_hex_color_validator])
# Typography
font_name = models.CharField(
max_length=200,
blank=True,
default="",
help_text="Font name or path for the name field. Searched in STATICFILES_DIRS, "
"STATIC_ROOT, then system fonts. Leave blank for Helvetica.",
)
font_body = models.CharField(
max_length=200,
blank=True,
default="",
help_text="Font name or path for body text (company, email). Leave blank for Helvetica.",
)
# Branding
logo = models.ImageField(upload_to="badges/logos/", blank=True, default="")
background_image = models.ImageField(
upload_to="badges/backgrounds/",
blank=True,
default="",
help_text="Custom background image from a designer. Overlaid text is rendered on top.",
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["name"]
unique_together = [("conference", "slug")]
constraints = [
models.UniqueConstraint(
fields=["conference"],
condition=models.Q(is_default=True),
name="registration_badgetemplate_one_default_per_conference",
),
]
def __str__(self) -> str:
return f"{self.name} ({self.conference.slug})"
[docs]
class Badge(models.Model):
"""A generated badge for a specific attendee.
Tracks the generated file, format, and timestamp so badges can be
cached and regenerated on demand.
"""
attendee = models.ForeignKey(
"program_registration.Attendee",
on_delete=models.CASCADE,
related_name="badges",
)
template = models.ForeignKey(
BadgeTemplate,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="badges",
)
format = models.CharField(
max_length=10,
choices=Format.choices,
default=Format.PDF,
)
file = models.FileField(upload_to="badges/generated/", blank=True, default="")
generated_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
def __str__(self) -> str:
return f"Badge for {self.attendee} ({self.format})"