Source code for django_program.conference.admin

"""Django admin configuration for the conference app."""

from django import forms
from django.contrib import admin

from django_program.conference.models import Conference, Expense, ExpenseCategory, FeatureFlags, KPITargets, Section

SECRET_PLACEHOLDER = "\u2022" * 12


[docs] class SecretInput(forms.PasswordInput): """Password widget that shows a placeholder instead of the real value. When a value already exists in the database the widget renders ``SECRET_PLACEHOLDER`` as the visible value so admins know a key is set, but the actual secret never appears in the HTML source. Submitting the placeholder (or an empty string) signals "keep existing value". """
[docs] def format_value(self, value: str | None) -> str: """Return a dot placeholder when a value exists, empty string otherwise.""" if value: return SECRET_PLACEHOLDER return ""
[docs] class SecretField(forms.CharField): """Char field that preserves the stored value when left unchanged. Works with ``SecretInput``: if the submitted value is empty or equals the placeholder, the field returns the original database value so secrets are never accidentally blanked. """ widget = SecretInput
[docs] def __init__(self, **kwargs: object) -> None: """Set sensible defaults for secret fields.""" kwargs.setdefault("required", False) super().__init__(**kwargs) self.widget.attrs.setdefault("autocomplete", "off")
[docs] def has_changed(self, initial: str | None, data: str | None) -> bool: """Treat placeholder or blank submissions as unchanged.""" if not data or data == SECRET_PLACEHOLDER: return False return super().has_changed(initial, data)
[docs] def clean(self, value: str | None) -> str | None: """Return the stored value when the field is left blank or unchanged.""" if not value or value == SECRET_PLACEHOLDER: return self.initial return super().clean(value)
_STRIPE_FIELDS = ("stripe_secret_key", "stripe_publishable_key", "stripe_webhook_secret")
[docs] class ConferenceForm(forms.ModelForm): """Custom form that masks Stripe secret fields in the admin. Uses ``SecretField`` / ``SecretInput`` so decrypted values are never present in the rendered HTML. Admins see a dot placeholder when a value is set; leaving the field unchanged preserves the stored secret. """ stripe_secret_key = SecretField() stripe_publishable_key = SecretField() stripe_webhook_secret = SecretField()
[docs] class Meta: model = Conference exclude: list[str] = []
[docs] class SectionInline(admin.TabularInline): """Inline editor for conference sections. Allows adding and editing sections directly from the conference admin change form. """ model = Section extra = 1 prepopulated_fields = {"slug": ("name",)} fields = ("name", "slug", "start_date", "end_date", "order")
[docs] class FeatureFlagsForm(forms.ModelForm): """Form for FeatureFlags that replaces 'Unknown' with 'Default (enabled)'. Each nullable boolean field defaults to ``None`` which means "use the value from ``DJANGO_PROGRAM['features']`` in settings". Since the out-of-the-box default for every feature is ``True``, the widget label reads "Default (enabled)" instead of Django's generic "Unknown". """
[docs] class Meta: model = FeatureFlags exclude: list[str] = []
[docs] def __init__(self, *args: object, **kwargs: object) -> None: """Replace 'Unknown' widget labels with 'Default (enabled)'.""" super().__init__(*args, **kwargs) for field in self.fields.values(): if isinstance(field.widget, forms.NullBooleanSelect): field.widget.choices = [ ("unknown", "Default (enabled)"), ("true", "Yes — force ON"), ("false", "No — force OFF"), ]
[docs] class FeatureFlagsInline(admin.StackedInline): """Inline editor for per-conference feature flag overrides. Allows toggling individual features directly from the conference admin change form. At most one ``FeatureFlags`` row per conference. """ model = FeatureFlags form = FeatureFlagsForm extra = 0 max_num = 1 fieldsets = ( ( "Module Toggles", { "fields": ( "registration_enabled", "sponsors_enabled", "travel_grants_enabled", "programs_enabled", "pretalx_sync_enabled", ), }, ), ( "UI Toggles", { "fields": ( "public_ui_enabled", "manage_ui_enabled", "all_ui_enabled", ), }, ), )
[docs] class KPITargetsInline(admin.StackedInline): """Inline editor for per-conference KPI target thresholds. Allows setting analytics dashboard targets directly from the conference admin change form. At most one row per conference. """ model = KPITargets extra = 0 max_num = 1 fieldsets = ( ( "Rate Targets (%)", { "fields": ( "target_conversion_rate", "target_refund_rate", "target_checkin_rate", "target_fulfillment_rate", "target_room_utilization", ), }, ), ( "Revenue Targets", { "fields": ("target_revenue_per_attendee",), }, ), )
[docs] @admin.register(Conference) class ConferenceAdmin(admin.ModelAdmin): """Admin interface for managing conferences. Groups fields into logical fieldsets: basic information, dates, third-party integrations (Pretalx and Stripe), and status metadata. Sections and feature flags are editable inline. """ form = ConferenceForm list_display = ("name", "slug", "start_date", "end_date", "is_active") list_filter = ("is_active",) search_fields = ("name", "slug") prepopulated_fields = {"slug": ("name",)} inlines = (SectionInline, FeatureFlagsInline, KPITargetsInline) fieldsets = ( ( None, { "fields": ("name", "slug", "venue", "address", "website_url", "total_capacity"), }, ), ( "Dates", { "fields": ("start_date", "end_date", "timezone"), }, ), ( "Integrations", { "fields": ( "pretalx_event_slug", "stripe_secret_key", "stripe_publishable_key", "stripe_webhook_secret", ), "classes": ("collapse",), }, ), ( "Status", { "fields": ("is_active",), }, ), )
[docs] @admin.register(Section) class SectionAdmin(admin.ModelAdmin): """Admin interface for managing conference sections. Provides filtering by conference and search by name or slug. The slug field is auto-populated from the section name. """ list_display = ("name", "conference", "start_date", "end_date", "order") list_filter = ("conference",) search_fields = ("name", "slug") prepopulated_fields = {"slug": ("name",)}
[docs] @admin.register(FeatureFlags) class FeatureFlagsAdmin(admin.ModelAdmin): """Standalone admin for per-conference feature flag overrides. Provides a list view for quick scanning across conferences. The same data is also editable inline on the Conference admin page. """ form = FeatureFlagsForm list_display = ( "conference", "registration_enabled", "sponsors_enabled", "travel_grants_enabled", "programs_enabled", "public_ui_enabled", "updated_at", ) list_filter = ("conference",) fieldsets = ( ("Conference", {"fields": ("conference",)}), ( "Module Toggles", { "fields": ( "registration_enabled", "sponsors_enabled", "travel_grants_enabled", "programs_enabled", "pretalx_sync_enabled", ), }, ), ( "UI Toggles", { "fields": ( "public_ui_enabled", "manage_ui_enabled", "all_ui_enabled", ), }, ), )
[docs] class ExpenseInline(admin.TabularInline): """Inline editor for expenses within the expense category admin.""" model = Expense extra = 0 fields = ("description", "amount", "vendor", "date", "receipt_reference", "created_by")
[docs] @admin.register(ExpenseCategory) class ExpenseCategoryAdmin(admin.ModelAdmin): """Admin interface for managing expense categories.""" list_display = ("name", "conference", "budget_amount", "order") list_filter = ("conference",) search_fields = ("name", "slug") prepopulated_fields = {"slug": ("name",)} inlines = (ExpenseInline,)
[docs] @admin.register(Expense) class ExpenseAdmin(admin.ModelAdmin): """Admin interface for managing individual expenses.""" list_display = ("description", "category", "conference", "amount", "vendor", "date", "created_by") list_filter = ("conference", "category", "date") search_fields = ("description", "vendor", "receipt_reference") raw_id_fields = ("created_by",) readonly_fields = ("created_at", "updated_at")
[docs] @admin.register(KPITargets) class KPITargetsAdmin(admin.ModelAdmin): """Standalone admin for per-conference KPI targets.""" list_display = ( "conference", "target_conversion_rate", "target_refund_rate", "target_checkin_rate", "target_fulfillment_rate", "updated_at", ) list_filter = ("conference",)