2019-01-02 01:44:57 +00:00
|
|
|
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):
|
2019-01-21 02:39:35 +00:00
|
|
|
"""Protects :model:`pinaxcon_raffle.DrawnTicket` from deletion if lock is in place."""
|
|
|
|
if instance.locked:
|
|
|
|
raise IntegrityError("Deleting a drawn ticket is not allowed.")
|
2019-01-02 01:44:57 +00:00
|
|
|
|
|
|
|
|
|
|
|
@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.")
|