Files @ fbefff60a6a4
Branch filter:

Location: symposion_app/pinaxcon/raffle/signals.py

bsturmfels
Update talk materials license
from itertools import chain
from random import sample

from django.db import IntegrityError
from django.db.models.signals import post_save, pre_save, pre_delete, post_init
from django.dispatch import receiver
from pinaxcon.raffle.models import DrawnTicket, Raffle, Draw, Prize


# Much of the following could be handled by directly overriding the
# relevant model methods. However, since `.objects.delete()` bypasses
# a model's delete() method but not its pre_ and post_delete signals,
# using signals gives us slightly better coverage of edge cases.
#
# In order to avoid mixing the two approaches we make extensive use of
# signals.


@receiver(post_save, sender=Draw)
def draw_raffle_tickets(sender, instance, created, **kwargs):
    """
    Draws tickets once a :model:`pinaxcon_raffle.Draw` instance
    has been created and prizes are still available.
    """
    if not created:
        return

    raffle = instance.raffle
    prizes = raffle.prizes.filter(winning_ticket__isnull=True)
    tickets = list(chain(*(ticket[1] for ticket in raffle.get_tickets())))
    if not tickets:
        return

    drawn_tickets = sample(tickets, len(prizes))

    for prize, ticket in zip(prizes, drawn_tickets):
        item_id = int(ticket.split('-')[0])

        drawn_ticket = DrawnTicket.objects.create(
            draw=instance,
            prize=prize,
            ticket=ticket,
            lineitem_id=item_id,
        )

        prize.winning_ticket = drawn_ticket
        prize.save(update_fields=('winning_ticket',))


@receiver(post_init, sender=Prize)
def set_prize_lock(sender, instance, **kwargs):
    """Locks :model:`pinaxcon_raffle.Prize` if a winner exists."""
    instance._locked = instance.winning_ticket is not None


@receiver(pre_save, sender=Prize)
def enforce_prize_lock(sender, instance, **kwargs):
    """Denies updates to :model:`pinaxcon_raffle.Prize` if lock is in place."""
    if instance.locked:
        raise IntegrityError("Updating a locked prize is not allowed.")


@receiver(pre_delete, sender=Prize)
def prevent_locked_prize_deletion(sender, instance, **kwargs):
    """Denies deletion of :model:`pinaxcon_raffle.Prize` if lock is in place."""
    if instance.locked:
        raise IntegrityError("Deleting a locked prize is not allowed.")


@receiver(pre_delete, sender=DrawnTicket)
def prevent_drawn_ticket_deletion(sender, instance, **kwargs):
    """Protects :model:`pinaxcon_raffle.DrawnTicket` from deletion if lock is in place."""
    if instance.locked:
        raise IntegrityError("Deleting a drawn ticket is not allowed.")


@receiver(pre_save, sender=DrawnTicket)
def prevent_drawn_ticket_update(sender, instance, **kwargs):
    """Protects :model:`pinaxcon_raffle.DrawnTicket` from updates."""
    if getattr(instance, 'pk', None):
        raise IntegrityError("Updating a drawn ticket is not allowed.")