172 lines
7.1 KiB
Python
172 lines
7.1 KiB
Python
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
from .models import TradeOffer, TradeAcceptance
|
|
from accounts.models import FriendCode
|
|
from cards.models import Card
|
|
from django.forms import ModelForm
|
|
from trades.models import TradeOfferHaveCard, TradeOfferWantCard
|
|
|
|
class NoValidationMultipleChoiceField(forms.MultipleChoiceField):
|
|
def validate(self, value):
|
|
# Override the validation to skip checking against defined choices
|
|
pass
|
|
|
|
class TradeOfferAcceptForm(forms.Form):
|
|
friend_code = forms.ModelChoiceField(
|
|
queryset=FriendCode.objects.none(),
|
|
label="Select a Friend Code to Accept This Trade Offer"
|
|
)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
# Expecting a keyword argument `friend_codes` with the user's friend codes.
|
|
friend_codes = kwargs.pop("friend_codes")
|
|
super().__init__(*args, **kwargs)
|
|
self.fields["friend_code"].queryset = friend_codes
|
|
|
|
class TradeAcceptanceCreateForm(forms.ModelForm):
|
|
"""
|
|
Form for creating a TradeAcceptance.
|
|
Expects the caller to pass:
|
|
- trade_offer: the instance of TradeOffer this acceptance is for.
|
|
- friend_codes: a queryset of FriendCode objects for the current user.
|
|
It filters available requested and offered cards based on what's still available.
|
|
"""
|
|
class Meta:
|
|
model = TradeAcceptance
|
|
fields = ["accepted_by", "requested_card", "offered_card"]
|
|
|
|
def __init__(self, *args, trade_offer=None, friend_codes=None, **kwargs):
|
|
if trade_offer is None:
|
|
raise ValueError("trade_offer must be provided to filter choices.")
|
|
super().__init__(*args, **kwargs)
|
|
self.trade_offer = trade_offer
|
|
|
|
# Filter accepted_by to those friend codes that belong to the current user.
|
|
if friend_codes is None:
|
|
raise ValueError("friend_codes must be provided")
|
|
self.fields["accepted_by"].queryset = friend_codes
|
|
|
|
# Update active_states to include only states that mean the acceptance is still "open".
|
|
active_states = [
|
|
TradeAcceptance.AcceptanceState.ACCEPTED,
|
|
TradeAcceptance.AcceptanceState.SENT,
|
|
TradeAcceptance.AcceptanceState.RECEIVED,
|
|
]
|
|
|
|
# Build available requested_card choices from the TradeOffer's "have" side.
|
|
available_requested_ids = []
|
|
for through_obj in trade_offer.trade_offer_have_cards.all():
|
|
active_count = trade_offer.acceptances.filter(
|
|
requested_card=through_obj.card,
|
|
state__in=active_states
|
|
).count()
|
|
if active_count < through_obj.quantity:
|
|
available_requested_ids.append(through_obj.card.id)
|
|
self.fields["requested_card"].queryset = Card.objects.filter(id__in=available_requested_ids)
|
|
|
|
# Similarly, build available offered_card choices from the TradeOffer's "want" side.
|
|
available_offered_ids = []
|
|
for through_obj in trade_offer.trade_offer_want_cards.all():
|
|
active_count = trade_offer.acceptances.filter(
|
|
offered_card=through_obj.card,
|
|
state__in=active_states
|
|
).count()
|
|
if active_count < through_obj.quantity:
|
|
available_offered_ids.append(through_obj.card.id)
|
|
self.fields["offered_card"].queryset = Card.objects.filter(id__in=available_offered_ids)
|
|
|
|
def clean(self):
|
|
"""
|
|
Ensure the instance has its trade_offer set before model-level validation occurs.
|
|
This prevents errors when the model clean() method attempts to access trade_offer.
|
|
"""
|
|
self.instance.trade_offer = self.trade_offer
|
|
return super().clean()
|
|
|
|
class ButtonRadioSelect(forms.RadioSelect):
|
|
template_name = "widgets/button_radio_select.html"
|
|
|
|
class TradeAcceptanceTransitionForm(forms.Form):
|
|
state = forms.ChoiceField(widget=forms.HiddenInput())
|
|
|
|
def __init__(self, *args, instance=None, user=None, **kwargs):
|
|
"""
|
|
Initializes the form with allowed transitions from the provided instance.
|
|
:param instance: A TradeAcceptance instance.
|
|
"""
|
|
super().__init__(*args, **kwargs)
|
|
if instance is None:
|
|
raise ValueError("A TradeAcceptance instance must be provided")
|
|
self.instance = instance
|
|
self.user = user
|
|
|
|
self.fields["state"].choices = instance.get_allowed_state_transitions(user)
|
|
|
|
class TradeOfferCreateForm(ModelForm):
|
|
# Override the default fields to capture quantity info in the format 'card_id:quantity'
|
|
have_cards = NoValidationMultipleChoiceField(widget=forms.SelectMultiple, required=True)
|
|
want_cards = NoValidationMultipleChoiceField(widget=forms.SelectMultiple, required=True)
|
|
|
|
class Meta:
|
|
model = TradeOffer
|
|
fields = ["want_cards", "have_cards", "initiated_by"]
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
# Populate choices from Card model
|
|
cards = Card.objects.order_by("name", "rarity__pk")
|
|
choices = [(str(card.pk), card.name) for card in cards]
|
|
self.fields["have_cards"].choices = choices
|
|
self.fields["want_cards"].choices = choices
|
|
|
|
def clean_have_cards(self):
|
|
data = self.data.getlist("have_cards")
|
|
parsed = {}
|
|
for item in data:
|
|
if ':' not in item:
|
|
# Ignore any input without a colon.
|
|
continue
|
|
parts = item.split(':')
|
|
card_id = parts[0]
|
|
try:
|
|
# Only parse quantity when a colon is present.
|
|
quantity = int(parts[1])
|
|
except ValueError:
|
|
raise forms.ValidationError(f"Invalid quantity provided in {item}")
|
|
parsed[card_id] = parsed.get(card_id, 0) + quantity
|
|
return parsed
|
|
|
|
def clean_want_cards(self):
|
|
data = self.data.getlist("want_cards")
|
|
parsed = {}
|
|
for item in data:
|
|
if ':' not in item:
|
|
continue
|
|
parts = item.split(':')
|
|
card_id = parts[0]
|
|
try:
|
|
quantity = int(parts[1])
|
|
except ValueError:
|
|
raise forms.ValidationError(f"Invalid quantity provided in {item}")
|
|
parsed[card_id] = parsed.get(card_id, 0) + quantity
|
|
return parsed
|
|
|
|
def save(self, commit=True):
|
|
instance = super().save(commit=False)
|
|
if commit:
|
|
instance.save()
|
|
# Clear any existing through model entries in case of update
|
|
TradeOfferHaveCard.objects.filter(trade_offer=instance).delete()
|
|
TradeOfferWantCard.objects.filter(trade_offer=instance).delete()
|
|
|
|
# Create through entries for have_cards
|
|
for card_id, quantity in self.cleaned_data["have_cards"].items():
|
|
card = Card.objects.get(pk=card_id)
|
|
TradeOfferHaveCard.objects.create(trade_offer=instance, card=card, quantity=quantity)
|
|
|
|
# Create through entries for want_cards
|
|
for card_id, quantity in self.cleaned_data["want_cards"].items():
|
|
card = Card.objects.get(pk=card_id)
|
|
TradeOfferWantCard.objects.create(trade_offer=instance, card=card, quantity=quantity)
|
|
|
|
return instance
|