progress on conversion to tailwind

This commit is contained in:
badblocks 2025-03-06 21:28:36 -08:00
parent 6a872124c6
commit 6e2843c60e
110 changed files with 4997 additions and 1691 deletions

View file

@ -1,61 +1,15 @@
from django import forms
from django.core.exceptions import ValidationError
from .models import TradeOffer
from friend_codes.models import FriendCode
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 TradeOfferUpdateForm(forms.ModelForm):
class Meta:
model = TradeOffer
# We now only edit the `state` field
fields = ["state"]
def __init__(self, *args, **kwargs):
"""
Expects additional keyword arguments:
- friend_codes: a list of friend code objects for the current user.
This initializer filters the available state choices based on:
- The current state's allowed transition.
- Which party (initiated_by or accepted_by) is acting.
"""
friend_codes = kwargs.pop("friend_codes")
super().__init__(*args, **kwargs)
instance = self.instance
allowed_state = None
# Define permitted transitions based on the current state and user role:
if instance.state == TradeOffer.State.INITIATED:
# Allow the accepted_by party to accept the trade.
if instance.accepted_by in friend_codes:
allowed_state = TradeOffer.State.ACCEPTED
elif instance.state == TradeOffer.State.ACCEPTED:
# Allow the initiated_by party to mark the trade as sent.
if instance.initiated_by in friend_codes:
allowed_state = TradeOffer.State.SENT
elif instance.state == TradeOffer.State.SENT:
# Allow the accepted_by party to mark the trade as received.
if instance.accepted_by in friend_codes:
allowed_state = TradeOffer.State.RECEIVED
if allowed_state:
# Limit the `state` field's choices to only the permitted transition.
label = dict(TradeOffer.State.choices)[allowed_state]
self.fields["state"].choices = [(allowed_state, label)]
else:
# If no valid transition is available for this user, remove the field.
self.fields.pop("state")
def clean_have_cards(self):
have_cards = self.cleaned_data.get("have_cards")
if have_cards:
for card in have_cards.all():
if card.rarity not in ALLOWED_RARITIES:
# Raising a ValidationError here will cause this error message to be shown beneath the 'have_cards' field.
raise ValidationError(
f"The card '{card}' has an invalid rarity: {card.rarity}. Allowed rarities are: {', '.join(ALLOWED_RARITIES)}."
)
return have_cards
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(
@ -68,3 +22,188 @@ class TradeOfferAcceptForm(forms.Form):
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
active_states = [
TradeAcceptance.AcceptanceState.ACCEPTED,
TradeAcceptance.AcceptanceState.SENT,
TradeAcceptance.AcceptanceState.RECEIVED,
TradeAcceptance.AcceptanceState.COMPLETED,
]
# 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 TradeAcceptanceUpdateForm(forms.ModelForm):
"""
Form for updating the state of an existing TradeAcceptance.
Based on the current state and which party is acting (initiator vs. acceptor),
this form limits available state transitions.
"""
class Meta:
model = TradeAcceptance
fields = ["state"]
def __init__(self, *args, friend_codes=None, **kwargs):
super().__init__(*args, **kwargs)
instance = self.instance
allowed_choices = []
# Allowed transitions for a TradeAcceptance:
# - From ACCEPTED:
# • If the initiator is acting, allow SENT and REJECTED_BY_INITIATOR.
# • If the acceptor is acting, allow REJECTED_BY_ACCEPTOR.
# - From SENT:
# • If the acceptor is acting, allow RECEIVED and REJECTED_BY_ACCEPTOR.
# • If the initiator is acting, allow REJECTED_BY_INITIATOR.
# - From RECEIVED:
# • If the initiator is acting, allow COMPLETED and REJECTED_BY_INITIATOR.
# • If the acceptor is acting, allow REJECTED_BY_ACCEPTOR.
if friend_codes is None:
raise ValueError("friend_codes must be provided")
if instance.state == TradeAcceptance.AcceptanceState.ACCEPTED:
if instance.trade_offer.initiated_by in friend_codes:
allowed_choices = [
(TradeAcceptance.AcceptanceState.SENT, "Sent"),
(TradeAcceptance.AcceptanceState.REJECTED_BY_INITIATOR, "Rejected by Initiator"),
]
elif instance.accepted_by in friend_codes:
allowed_choices = [
(TradeAcceptance.AcceptanceState.REJECTED_BY_ACCEPTOR, "Rejected by Acceptor"),
]
elif instance.state == TradeAcceptance.AcceptanceState.SENT:
if instance.accepted_by in friend_codes:
allowed_choices = [
(TradeAcceptance.AcceptanceState.RECEIVED, "Received"),
(TradeAcceptance.AcceptanceState.REJECTED_BY_ACCEPTOR, "Rejected by Acceptor"),
]
elif instance.trade_offer.initiated_by in friend_codes:
allowed_choices = [
(TradeAcceptance.AcceptanceState.REJECTED_BY_INITIATOR, "Rejected by Initiator"),
]
elif instance.state == TradeAcceptance.AcceptanceState.RECEIVED:
if instance.trade_offer.initiated_by in friend_codes:
allowed_choices = [
(TradeAcceptance.AcceptanceState.COMPLETED, "Completed"),
(TradeAcceptance.AcceptanceState.REJECTED_BY_INITIATOR, "Rejected by Initiator"),
]
elif instance.accepted_by in friend_codes:
allowed_choices = [
(TradeAcceptance.AcceptanceState.REJECTED_BY_ACCEPTOR, "Rejected by Acceptor"),
]
if allowed_choices:
self.fields["state"].choices = allowed_choices
else:
self.fields.pop("state")
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:
parts = item.split(':')
card_id = parts[0]
try:
quantity = int(parts[1]) if len(parts) > 1 else 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:
parts = item.split(':')
card_id = parts[0]
try:
quantity = int(parts[1]) if len(parts) > 1 else 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