from django.core.exceptions import ValidationError from django.db.models.signals import m2m_changed, post_save, post_delete, pre_save from django.dispatch import receiver from .models import TradeOffer from cards.models import Card from django.db.models import F from trades.models import TradeOfferHaveCard, TradeOfferWantCard, TradeAcceptance from django.db import transaction def validate_and_set_trade_offer_rarity(instance): """ Ensures all cards on both sides share the same rarity and sets the TradeOffer's rarity_level and rarity_icon if they haven't been set already. """ combined_cards = list(instance.have_cards.all()) + list(instance.want_cards.all()) if not combined_cards: return rarities = {card.rarity_level for card in combined_cards} if len(rarities) > 1: raise ValidationError("All cards in a trade offer must have the same rarity.") updated_fields = [] if instance.rarity_level is None: instance.rarity_level = combined_cards[0].rarity_level updated_fields.append("rarity_level") if instance.rarity_icon is None: instance.rarity_icon = combined_cards[0].rarity_icon updated_fields.append("rarity_icon") if updated_fields: instance.save(update_fields=updated_fields) @receiver(m2m_changed, sender=TradeOffer.have_cards.through) def validate_have_cards_rarity(sender, instance, action, **kwargs): if action == "post_add": transaction.on_commit(lambda: validate_and_set_trade_offer_rarity(instance)) @receiver(m2m_changed, sender=TradeOffer.want_cards.through) def validate_want_cards_rarity(sender, instance, action, **kwargs): if action == "post_add": transaction.on_commit(lambda: validate_and_set_trade_offer_rarity(instance)) ACTIVE_STATES = [ TradeAcceptance.AcceptanceState.ACCEPTED, TradeAcceptance.AcceptanceState.SENT, TradeAcceptance.AcceptanceState.RECEIVED, TradeAcceptance.AcceptanceState.THANKED_BY_INITIATOR, TradeAcceptance.AcceptanceState.THANKED_BY_ACCEPTOR, TradeAcceptance.AcceptanceState.THANKED_BY_BOTH, ] def adjust_qty_for_trade_offer(trade_offer, card, side, delta): """ Increment (or decrement) qty_accepted by delta for the given card on the specified side. """ if side == 'have': TradeOfferHaveCard.objects.filter( trade_offer=trade_offer, card=card ).update(qty_accepted=F('qty_accepted') + delta) elif side == 'want': TradeOfferWantCard.objects.filter( trade_offer=trade_offer, card=card ).update(qty_accepted=F('qty_accepted') + delta) def update_trade_offer_closed_status(trade_offer): """ Check if both sides of the trade offer meet the quantity requirement. Mark the trade_offer as closed if all cards have qty_accepted greater than or equal to quantity; otherwise, mark it as open. """ have_complete = not TradeOfferHaveCard.objects.filter( trade_offer=trade_offer, qty_accepted__lt=F('quantity') ).exists() want_complete = not TradeOfferWantCard.objects.filter( trade_offer=trade_offer, qty_accepted__lt=F('quantity') ).exists() closed = have_complete or want_complete if trade_offer.is_closed != closed: trade_offer.is_closed = closed trade_offer.save(update_fields=["is_closed"]) # Pre-save signal to capture the original state before any changes. @receiver(pre_save, sender=TradeAcceptance) def trade_acceptance_pre_save(sender, instance, **kwargs): if instance.pk: old_instance = TradeAcceptance.objects.get(pk=instance.pk) instance._old_state = old_instance.state # Post-save signal to adjust qty_accepted incrementally. @receiver(post_save, sender=TradeAcceptance) def trade_acceptance_post_save(sender, instance, created, **kwargs): delta = 0 if created: # For a new acceptance, increment only if the state is active. if instance.state in ACTIVE_STATES: delta = 1 else: old_state = getattr(instance, '_old_state', None) if old_state is not None: # Transition from active to non-active (e.g. a rejection) if old_state in ACTIVE_STATES and instance.state not in ACTIVE_STATES: delta = -1 # Transition from non-active to active elif old_state not in ACTIVE_STATES and instance.state in ACTIVE_STATES: delta = 1 if delta != 0: trade_offer = instance.trade_offer # Update the "have" side using the requested_card. adjust_qty_for_trade_offer(trade_offer, instance.requested_card, side='have', delta=delta) # Update the "want" side using the offered_card. adjust_qty_for_trade_offer(trade_offer, instance.offered_card, side='want', delta=delta) update_trade_offer_closed_status(trade_offer) # Post-delete signal to decrement qty_accepted if the deleted acceptance was active. @receiver(post_delete, sender=TradeAcceptance) def trade_acceptance_post_delete(sender, instance, **kwargs): if instance.state in ACTIVE_STATES: delta = -1 trade_offer = instance.trade_offer adjust_qty_for_trade_offer(trade_offer, instance.requested_card, side='have', delta=delta) adjust_qty_for_trade_offer(trade_offer, instance.offered_card, side='want', delta=delta) update_trade_offer_closed_status(trade_offer)