Files
@ e58a1891d554
Branch filter:
Location: symposion_app/pinaxcon/raffle/signals.py - annotation
e58a1891d554
2.8 KiB
text/x-python
Add migration for profile change, update CONFERENCE_ID for 2024
d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c 970ec9184c57 970ec9184c57 970ec9184c57 d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c d1ff8d72533c | 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.")
|