Add rarity field to trade_offer instead of looking up via cards

This commit is contained in:
badblocks 2025-03-16 19:06:36 -07:00
parent ba33139993
commit f7a9b2f823
13 changed files with 87 additions and 50 deletions

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-16 04:58
# Generated by Django 5.1.2 on 2025-03-16 18:18
import django.db.models.deletion
from django.db import migrations, models
@ -23,6 +23,7 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('initiated_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='initiated_trade_offers', to='accounts.friendcode')),
('rarity', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to='cards.rarity')),
],
),
migrations.CreateModel(

View file

@ -7,18 +7,18 @@ from accounts.models import FriendCode
class TradeOfferManager(models.Manager):
def get_queryset(self):
queryset = super().get_queryset().select_related("initiated_by", "initiated_by__user")
queryset = super().get_queryset().select_related("initiated_by", "initiated_by__user", "rarity")
queryset = queryset.prefetch_related(
Prefetch(
"trade_offer_want_cards",
queryset=TradeOfferWantCard.objects.select_related("card").annotate(
queryset=TradeOfferWantCard.objects.select_related("card").prefetch_related('card__decks').annotate(
total_quantity=Sum("quantity"),
total_accepted=Sum("qty_accepted")
).order_by("total_quantity", "id")
),
Prefetch(
"trade_offer_have_cards",
queryset=TradeOfferHaveCard.objects.select_related("card").annotate(
queryset=TradeOfferHaveCard.objects.select_related("card").prefetch_related('card__decks').annotate(
total_quantity=Sum("quantity"),
total_accepted=Sum("qty_accepted")
).order_by("total_quantity", "id")
@ -42,7 +42,14 @@ class TradeOffer(models.Model):
on_delete=models.PROTECT,
related_name='initiated_trade_offers'
)
# Use custom through models to support multiples.
rarity = models.ForeignKey(
"cards.Rarity",
on_delete=models.PROTECT,
null=True,
blank=True,
editable=False,
db_index=True
)
want_cards = models.ManyToManyField(
"cards.Card",
related_name='trade_offers_want',

View file

@ -6,22 +6,35 @@ from cards.models import Card
from django.db.models import F
from trades.models import TradeOfferHaveCard, TradeOfferWantCard, TradeAcceptance
def check_trade_offer_rarity(instance):
def validate_and_set_trade_offer_rarity(instance):
"""
Ensures all cards on both sides share the same rarity and sets the TradeOffer.rarity
if it hasn't been set already.
"""
# Combine cards from both sides.
combined_cards = list(instance.have_cards.all()) + list(instance.want_cards.all())
# Use the normalized rarity from each card
if not combined_cards:
return
# Gather the Rarity instances from the cards.
rarities = {card.normalized_rarity for card in combined_cards}
if len(rarities) > 1:
raise ValidationError("All cards in a trade offer must have the same rarity.")
# If trade offer's rarity isn't set yet, update it.
if instance.rarity is None:
instance.rarity = combined_cards[0].normalized_rarity
instance.save(update_fields=["rarity"])
@receiver(m2m_changed, sender=TradeOffer.have_cards.through)
def validate_have_cards_rarity(sender, instance, action, **kwargs):
if action == "post_add":
check_trade_offer_rarity(instance)
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":
check_trade_offer_rarity(instance)
validate_and_set_trade_offer_rarity(instance)
ACTIVE_STATES = [
TradeAcceptance.AcceptanceState.ACCEPTED,

View file

@ -6,19 +6,19 @@ register = template.Library()
def render_trade_offer(context, offer):
"""
Renders a trade offer including detailed trade acceptance information.
Groups acceptances for each card on both the have and want sides.
Freezes the through-model querysets to avoid extra DB hits.
"""
have_cards_available = []
want_cards_available = []
trade_offer_have_cards = list(offer.trade_offer_have_cards.all())
trade_offer_want_cards = list(offer.trade_offer_want_cards.all())
for card in offer.trade_offer_have_cards.all():
if (card.quantity > card.qty_accepted):
have_cards_available.append(card)
for card in offer.trade_offer_want_cards.all():
if (card.quantity > card.qty_accepted):
want_cards_available.append(card)
have_cards_available = [
card for card in trade_offer_have_cards
if card.quantity > card.qty_accepted
]
want_cards_available = [
card for card in trade_offer_want_cards
if card.quantity > card.qty_accepted
]
return {
'offer': offer,

View file

@ -16,6 +16,7 @@ from .models import TradeOffer, TradeAcceptance
from .forms import (TradeOfferAcceptForm,
TradeAcceptanceCreateForm, TradeOfferCreateForm, TradeAcceptanceTransitionForm)
from cards.models import Card
from silk.profiling.profiler import silk_profile
class TradeOfferCreateView(LoginRequiredMixin, CreateView):
model = TradeOffer
@ -67,6 +68,7 @@ class TradeOfferAllListView(ListView):
model = TradeOffer
template_name = "trades/trade_offer_all_list.html"
@silk_profile(name="Trade Offer All List- Get Context Data")
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(**kwargs)
request = self.request
@ -86,6 +88,7 @@ class TradeOfferAllListView(ListView):
context["all_trade_offers_paginated"] = offers_paginator.get_page(offers_page)
return context
@silk_profile(name="Trade Offer All List- Render to Response")
def render_to_response(self, context, **response_kwargs):
# For AJAX requests, return only the paginated fragment.
if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
@ -182,6 +185,7 @@ class TradeOfferMyListView(LoginRequiredMixin, ListView):
other_acceptances = involved_acceptances.exclude(pk__in=waiting_acceptances.values("pk"))
return Paginator(other_acceptances, 10).get_page(page_param)
@silk_profile(name="Trade Offer My List- Get Context Data")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
request = self.request
@ -202,6 +206,7 @@ class TradeOfferMyListView(LoginRequiredMixin, ListView):
return context
@silk_profile(name="Trade Offer My List- Render to Response")
def render_to_response(self, context, **response_kwargs):
# For AJAX requests, return only the paginated fragment.
if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
@ -313,6 +318,7 @@ class TradeOfferSearchView(ListView):
results.append((card_id, qty))
return results
@silk_profile(name="Trade Offer Search- Get Queryset")
def get_queryset(self):
from django.db.models import F
# For a GET request (initial load), return an empty queryset.
@ -349,10 +355,12 @@ class TradeOfferSearchView(ListView):
return qs.distinct()
@silk_profile(name="Trade Offer Search- Post")
def post(self, request, *args, **kwargs):
# For POST, simply process the search through get().
return self.get(request, *args, **kwargs)
@silk_profile(name="Trade Offer Search- Get Context Data")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
from cards.models import Card
@ -367,6 +375,7 @@ class TradeOfferSearchView(ListView):
context["wanted_cards"] = []
return context
@silk_profile(name="Trade Offer Search- Render to Response")
def render_to_response(self, context, **response_kwargs):
"""
Render the AJAX fragment if the request is AJAX; otherwise, render the complete page.
@ -386,6 +395,7 @@ class TradeOfferDetailView(LoginRequiredMixin, DetailView):
model = TradeOffer
template_name = "trades/trade_offer_detail.html"
@silk_profile(name="Trade Offer Detail- Get Context Data")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
trade_offer = self.get_object()
@ -446,18 +456,7 @@ class TradeAcceptanceCreateView(LoginRequiredMixin, CreateView):
def get_trade_offer(self):
return (
TradeOffer.objects.select_related('initiated_by')
.prefetch_related(
'trade_offer_want_cards__card',
'trade_offer_have_cards__card',
Prefetch(
'acceptances',
queryset=TradeAcceptance.objects.select_related(
'accepted_by', 'requested_card', 'offered_card'
)
)
)
.get(pk=self.kwargs['offer_pk'])
TradeOffer.objects.get(pk=self.kwargs['offer_pk'])
)
def get_form_kwargs(self):