Source code for django_program.manage.forms_bulk_purchases

"""Forms for bulk purchase management in the organizer dashboard."""

from typing import override

from django import forms

from django_program.sponsors.models import BulkPurchase


[docs] class BulkPurchaseCreateForm(forms.ModelForm): """Form for organizers to create a bulk purchase deal. Product-first layout: what's being sold, how many codes, what discount, who gets it (optionally a sponsor), and validity window. """ voucher_type = forms.ChoiceField( label="Discount type", choices=[ ("comp", "Complimentary (100% off)"), ("percentage", "Percentage discount"), ("fixed_amount", "Fixed amount off"), ], initial="comp", help_text="How the discount is applied to each voucher code.", ) discount_value = forms.DecimalField( label="Discount amount", max_digits=10, decimal_places=2, initial=0, help_text="Percentage (0-100) or fixed dollar amount, depending on the discount type above.", widget=forms.NumberInput(attrs={"placeholder": "0.00", "step": "0.01", "min": "0"}), ) max_uses_per_voucher = forms.IntegerField( label="Uses per code", min_value=1, initial=1, help_text="How many times each generated code can be redeemed (usually 1).", widget=forms.NumberInput(attrs={"min": "1", "placeholder": "1"}), ) valid_from = forms.DateTimeField( required=False, widget=forms.DateTimeInput( attrs={"type": "datetime-local"}, format="%Y-%m-%dT%H:%M", ), help_text="Start of voucher validity window (leave blank for immediately valid).", ) valid_until = forms.DateTimeField( required=False, widget=forms.DateTimeInput( attrs={"type": "datetime-local"}, format="%Y-%m-%dT%H:%M", ), help_text="End of voucher validity window (leave blank for no expiry).", )
[docs] class Meta: model = BulkPurchase fields = [ "product_description", "ticket_type", "addon", "quantity", "unit_price", "sponsor", ] labels = { "product_description": "Deal name", "quantity": "Number of codes", "unit_price": "Price per unit", "sponsor": "Sponsor", "ticket_type": "Ticket type", "addon": "Add-on", } help_texts = { "product_description": ( "A short name for this deal, e.g. 'PyCon 2077 Tutorial Bundle', 'Staff T-Shirt Pack'." ), "quantity": "How many unique voucher codes to generate.", "unit_price": "The price the buyer pays per item (before any voucher discount).", "sponsor": "Optionally associate this deal with a sponsor. Leave blank for non-sponsor deals.", "ticket_type": "The ticket type these voucher codes apply to.", "addon": "The add-on these voucher codes apply to (e.g. tutorials, t-shirts).", } widgets = { "product_description": forms.TextInput( attrs={"placeholder": "e.g. Gold Sponsor Comp Tickets"}, ), "quantity": forms.NumberInput(attrs={"min": "1", "max": "500", "placeholder": "10"}), "unit_price": forms.NumberInput(attrs={"step": "0.01", "min": "0", "placeholder": "0.00"}), }
[docs] def clean_quantity(self) -> int: """Validate that quantity does not exceed the voucher service limit.""" quantity = self.cleaned_data["quantity"] max_bulk_codes = 500 if quantity > max_bulk_codes: raise forms.ValidationError(f"Cannot generate more than {max_bulk_codes} voucher codes per bulk purchase.") return quantity
[docs] def clean(self) -> dict[str, object]: """Compute total_amount and pack voucher_config from extra fields.""" cleaned = super().clean() quantity = cleaned.get("quantity") unit_price = cleaned.get("unit_price") if quantity and unit_price: cleaned["total_amount"] = unit_price * quantity return cleaned
[docs] @override def save(self, commit: bool = True) -> BulkPurchase: """Populate computed fields before saving.""" instance = super().save(commit=False) data = self.cleaned_data instance.total_amount = data.get("total_amount", instance.unit_price * instance.quantity) instance.voucher_config = { "voucher_type": data["voucher_type"], "discount_value": str(data["discount_value"]), "max_uses": data["max_uses_per_voucher"], "valid_from": data["valid_from"].isoformat() if data.get("valid_from") else None, "valid_until": data["valid_until"].isoformat() if data.get("valid_until") else None, } if commit: instance.save() return instance