Fix create trade offer flow and other related bugs
This commit is contained in:
parent
f3a1366269
commit
65ca344582
40 changed files with 867 additions and 278 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 5.1.2 on 2025-03-20 00:08
|
||||
# Generated by Django 5.1.2 on 2025-03-22 04:08
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
|
|
|||
23
trades/mixins.py
Normal file
23
trades/mixins.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from cards.models import Card
|
||||
|
||||
class TradeOfferContextMixin:
|
||||
def get_context_data(self, **kwargs):
|
||||
# Start with any context passed in.
|
||||
context = kwargs.copy()
|
||||
# Include available cards requirements for multiselect fields.
|
||||
context.setdefault("cards", Card.objects.all().order_by("name", "rarity_level"))
|
||||
|
||||
# Provide friend_codes and selected_friend_code as in TradeOfferCreateView
|
||||
friend_codes = self.request.user.friend_codes.all()
|
||||
context["friend_codes"] = friend_codes
|
||||
|
||||
if "initiated_by" in self.request.GET:
|
||||
try:
|
||||
selected_friend_code = friend_codes.get(pk=self.request.GET.get("initiated_by"))
|
||||
except friend_codes.model.DoesNotExist:
|
||||
selected_friend_code = self.request.user.default_friend_code or friend_codes.first()
|
||||
else:
|
||||
selected_friend_code = self.request.user.default_friend_code or friend_codes.first()
|
||||
context["selected_friend_code"] = selected_friend_code
|
||||
|
||||
return context
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q, Count, Prefetch, F, Sum
|
||||
from django.db.models import Q, Count, Prefetch, F, Sum, Max
|
||||
import hashlib
|
||||
from cards.models import Card
|
||||
from accounts.models import FriendCode
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
|
||||
class TradeOfferManager(models.Manager):
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().select_related(
|
||||
qs = super().get_queryset().select_related(
|
||||
"initiated_by__user",
|
||||
).prefetch_related(
|
||||
"trade_offer_have_cards__card",
|
||||
|
|
@ -16,8 +19,11 @@ class TradeOfferManager(models.Manager):
|
|||
"acceptances__requested_card",
|
||||
"acceptances__offered_card",
|
||||
"acceptances__accepted_by__user",
|
||||
).order_by("-updated_at")
|
||||
return queryset
|
||||
)
|
||||
|
||||
cutoff = timezone.now() - timedelta(days=28)
|
||||
qs = qs.filter(created_at__gte=cutoff)
|
||||
return qs.order_by("-updated_at")
|
||||
|
||||
class TradeOffer(models.Model):
|
||||
objects = TradeOfferManager()
|
||||
|
|
@ -57,6 +63,29 @@ class TradeOffer(models.Model):
|
|||
self.hash = hashlib.md5((str(self.id) + "z").encode("utf-8")).hexdigest()[:8] + "z"
|
||||
super().save(update_fields=["hash"])
|
||||
|
||||
def update_rarity_fields(self):
|
||||
"""
|
||||
Recalculates and updates the rarity_level and rarity_icon fields based on
|
||||
the associated have_cards and want_cards.
|
||||
|
||||
Enforces that all cards in the trade offer share the same rarity.
|
||||
Uses the first card's rarity details to update both fields.
|
||||
"""
|
||||
# Gather all cards from both sides.
|
||||
cards = list(self.have_cards.all()) + list(self.want_cards.all())
|
||||
if not cards:
|
||||
return
|
||||
# Enforce same rarity across all cards.
|
||||
rarity_levels = {card.rarity_level for card in cards}
|
||||
if len(rarity_levels) > 1:
|
||||
raise ValidationError("All cards in a trade offer must have the same rarity.")
|
||||
first_card = cards[0]
|
||||
if self.rarity_level != first_card.rarity_level or self.rarity_icon != first_card.rarity_icon:
|
||||
self.rarity_level = first_card.rarity_level
|
||||
self.rarity_icon = first_card.rarity_icon
|
||||
# Use super().save() here to avoid recursion.
|
||||
super(TradeOffer, self).save(update_fields=["rarity_level", "rarity_icon"])
|
||||
|
||||
class TradeOfferHaveCard(models.Model):
|
||||
"""
|
||||
Through model for TradeOffer.have_cards.
|
||||
|
|
@ -70,12 +99,20 @@ class TradeOfferHaveCard(models.Model):
|
|||
)
|
||||
card = models.ForeignKey("cards.Card", on_delete=models.PROTECT, db_index=True)
|
||||
quantity = models.PositiveIntegerField(default=1)
|
||||
# New field to track number of accepted cards for this entry.
|
||||
qty_accepted = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.card.name} x{self.quantity} (Accepted: {self.qty_accepted})"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
self.trade_offer.update_rarity_fields()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
trade_offer = self.trade_offer
|
||||
super().delete(*args, **kwargs)
|
||||
trade_offer.update_rarity_fields()
|
||||
|
||||
class Meta:
|
||||
unique_together = ("trade_offer", "card")
|
||||
|
||||
|
|
@ -91,12 +128,20 @@ class TradeOfferWantCard(models.Model):
|
|||
)
|
||||
card = models.ForeignKey("cards.Card", on_delete=models.PROTECT)
|
||||
quantity = models.PositiveIntegerField(default=1)
|
||||
# New field for tracking accepted count.
|
||||
qty_accepted = models.PositiveIntegerField(default=0, editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.card.name} x{self.quantity} (Accepted: {self.qty_accepted})"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
self.trade_offer.update_rarity_fields()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
trade_offer = self.trade_offer
|
||||
super().delete(*args, **kwargs)
|
||||
trade_offer.update_rarity_fields()
|
||||
|
||||
class Meta:
|
||||
unique_together = ("trade_offer", "card")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,45 +1,9 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.db.models.signals import m2m_changed, post_save, post_delete, pre_save
|
||||
from django.db.models.signals import 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,
|
||||
|
|
@ -83,40 +47,32 @@ def update_trade_offer_closed_status(trade_offer):
|
|||
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:
|
||||
|
|
@ -124,4 +80,4 @@ def trade_acceptance_post_delete(sender, instance, **kwargs):
|
|||
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)
|
||||
update_trade_offer_closed_status(trade_offer)
|
||||
|
|
@ -3,7 +3,7 @@ from django import template
|
|||
register = template.Library()
|
||||
|
||||
@register.inclusion_tag('templatetags/trade_offer.html', takes_context=True)
|
||||
def render_trade_offer(context, offer, screenshot_mode=False):
|
||||
def render_trade_offer(context, offer, screenshot_mode=False, show_friend_code=False, expanded=False):
|
||||
"""
|
||||
Renders a trade offer including detailed trade acceptance information.
|
||||
Freezes the through-model querysets to avoid extra DB hits.
|
||||
|
|
@ -26,6 +26,7 @@ def render_trade_offer(context, offer, screenshot_mode=False):
|
|||
|
||||
return {
|
||||
'offer_pk': offer.pk,
|
||||
'expanded': expanded,
|
||||
'offer_hash': offer.hash,
|
||||
'rarity_icon': offer.rarity_icon,
|
||||
'initiated_by_email': offer.initiated_by.user.email,
|
||||
|
|
@ -36,6 +37,7 @@ def render_trade_offer(context, offer, screenshot_mode=False):
|
|||
'screenshot_mode': screenshot_mode,
|
||||
'in_game_name': offer.initiated_by.in_game_name,
|
||||
'friend_code': offer.initiated_by.friend_code,
|
||||
'show_friend_code': show_friend_code,
|
||||
'num_cards_available': len(have_cards_available) + len(want_cards_available),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
from django.urls import path
|
||||
|
||||
from django.views.decorators.cache import cache_page
|
||||
from .views import (
|
||||
TradeOfferCreateView,
|
||||
TradeOfferCreateConfirmView,
|
||||
TradeOfferAllListView,
|
||||
TradeOfferMyListView,
|
||||
TradeOfferDetailView,
|
||||
|
|
@ -14,11 +15,12 @@ from .views import (
|
|||
|
||||
urlpatterns = [
|
||||
path("create/", TradeOfferCreateView.as_view(), name="trade_offer_create"),
|
||||
path("create/confirm/", TradeOfferCreateConfirmView.as_view(), name="trade_offer_confirm_create"),
|
||||
path("all/", TradeOfferAllListView.as_view(), name="trade_offer_list"),
|
||||
path("my/", TradeOfferMyListView.as_view(), name="trade_offer_my_list"),
|
||||
path("search/", TradeOfferSearchView.as_view(), name="trade_offer_search"),
|
||||
path("<int:pk>/", TradeOfferDetailView.as_view(), name="trade_offer_detail"),
|
||||
path("<int:pk>.png", TradeOfferPNGView.as_view(), name="trade_offer_png"),
|
||||
path("<int:pk>.png", cache_page(15)(TradeOfferPNGView.as_view()), name="trade_offer_png"),
|
||||
path("delete/<int:pk>/", TradeOfferDeleteView.as_view(), name="trade_offer_delete"),
|
||||
path("offer/<int:offer_pk>", TradeAcceptanceCreateView.as_view(), name="trade_acceptance_create"),
|
||||
path("accept/<int:pk>/", TradeAcceptanceUpdateView.as_view(), name="trade_acceptance_update"),
|
||||
|
|
|
|||
215
trades/views.py
215
trades/views.py
|
|
@ -12,7 +12,7 @@ from django.utils.decorators import method_decorator
|
|||
from django.views.decorators.http import require_http_methods
|
||||
from django.core.paginator import Paginator
|
||||
from django.contrib import messages
|
||||
|
||||
from django.views.decorators.cache import cache_page
|
||||
from meta.views import Meta
|
||||
from .models import TradeOffer, TradeAcceptance
|
||||
from .forms import (TradeOfferAcceptForm,
|
||||
|
|
@ -27,6 +27,7 @@ from playwright.sync_api import sync_playwright
|
|||
from django.conf import settings
|
||||
|
||||
class TradeOfferCreateView(LoginRequiredMixin, CreateView):
|
||||
http_method_names = ['get'] # restricts this view to GET only
|
||||
model = TradeOffer
|
||||
form_class = TradeOfferCreateForm
|
||||
template_name = "trades/trade_offer_create.html"
|
||||
|
|
@ -63,13 +64,6 @@ class TradeOfferCreateView(LoginRequiredMixin, CreateView):
|
|||
context["selected_friend_code"] = selected_friend_code
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
friend_codes = self.request.user.friend_codes.all()
|
||||
if form.cleaned_data.get("initiated_by") not in friend_codes:
|
||||
raise PermissionDenied("You cannot initiate trade offers for friend codes that do not belong to you.")
|
||||
self.object = form.save()
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
class TradeOfferAllListView(ListView):
|
||||
model = TradeOffer
|
||||
template_name = "trades/trade_offer_all_list.html"
|
||||
|
|
@ -326,17 +320,16 @@ class TradeOfferSearchView(ListView):
|
|||
|
||||
#@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.
|
||||
if self.request.method == "GET":
|
||||
return TradeOffer.objects.none()
|
||||
|
||||
# Parse the POST data for offered and wanted selections.
|
||||
offered_selections = self.parse_selections(self.request.POST.getlist("offered_cards"))
|
||||
wanted_selections = self.parse_selections(self.request.POST.getlist("wanted_cards"))
|
||||
have_selections = self.parse_selections(self.request.POST.getlist("have_cards"))
|
||||
want_selections = self.parse_selections(self.request.POST.getlist("want_cards"))
|
||||
|
||||
# If no selections are provided, return an empty queryset.
|
||||
if not offered_selections and not wanted_selections:
|
||||
if not have_selections and not want_selections:
|
||||
return TradeOffer.objects.none()
|
||||
|
||||
qs = TradeOffer.objects.filter(
|
||||
|
|
@ -344,16 +337,16 @@ class TradeOfferSearchView(ListView):
|
|||
).exclude(initiated_by__in=self.request.user.friend_codes.all())
|
||||
|
||||
# Chain filters for offered selections (i.e. the user "has" cards).
|
||||
if offered_selections:
|
||||
for card_id, qty in offered_selections:
|
||||
if have_selections:
|
||||
for card_id, qty in have_selections:
|
||||
qs = qs.filter(
|
||||
trade_offer_want_cards__card_id=card_id,
|
||||
trade_offer_want_cards__quantity__gte=qty,
|
||||
)
|
||||
|
||||
# Chain filters for wanted selections (i.e. the user "wants" cards).
|
||||
if wanted_selections:
|
||||
for card_id, qty in wanted_selections:
|
||||
if want_selections:
|
||||
for card_id, qty in want_selections:
|
||||
qs = qs.filter(
|
||||
trade_offer_have_cards__card_id=card_id,
|
||||
trade_offer_have_cards__quantity__gte=qty,
|
||||
|
|
@ -373,11 +366,11 @@ class TradeOfferSearchView(ListView):
|
|||
# Populate available_cards to re-populate the multiselects.
|
||||
context["cards"] = Card.objects.all().order_by("name")
|
||||
if self.request.method == "POST":
|
||||
context["offered_cards"] = self.request.POST.getlist("offered_cards")
|
||||
context["wanted_cards"] = self.request.POST.getlist("wanted_cards")
|
||||
context["have_cards"] = self.request.POST.getlist("have_cards")
|
||||
context["want_cards"] = self.request.POST.getlist("want_cards")
|
||||
else:
|
||||
context["offered_cards"] = []
|
||||
context["wanted_cards"] = []
|
||||
context["have_cards"] = []
|
||||
context["want_cards"] = []
|
||||
return context
|
||||
|
||||
#@silk_profile(name="Trade Offer Search- Render to Response")
|
||||
|
|
@ -405,6 +398,8 @@ class TradeOfferDetailView(DetailView):
|
|||
context = super().get_context_data(**kwargs)
|
||||
trade_offer = self.get_object()
|
||||
screenshot_mode = self.request.GET.get("screenshot_mode")
|
||||
if screenshot_mode:
|
||||
context["show_friend_code"] = trade_offer.initiated_by.user.show_friend_code_on_link_previews
|
||||
context["screenshot_mode"] = screenshot_mode
|
||||
|
||||
# Calculate the number of cards in each category.
|
||||
|
|
@ -418,14 +413,14 @@ class TradeOfferDetailView(DetailView):
|
|||
# Calculate a base height using our previous assumptions:
|
||||
# - 80px per card row (with rows computed as round(num_cards/2))
|
||||
# - plus 138px for header/footer.
|
||||
base_height = (round(num_cards / 2) * 80) + 138
|
||||
base_height = (round(num_cards / 2) * 56) + 138
|
||||
|
||||
# Calculate a base width by assuming two columns of card badges.
|
||||
# Here we assume each card badge is 80px wide plus the same horizontal offset of 138px.
|
||||
if (num_wants + num_has) >= 3:
|
||||
base_width = ((num_wants + num_has) * 144) + 96
|
||||
else:
|
||||
if (num_wants + num_has) >= 4:
|
||||
base_width = (4 * 144) + 96
|
||||
else:
|
||||
base_width = (2 * 144) + 128
|
||||
|
||||
if base_height > base_width:
|
||||
# The trade-offer card is taller than wide;
|
||||
|
|
@ -610,6 +605,7 @@ class TradeOfferPNGView(View):
|
|||
This view loads the trade offer detail page, waits for the trade offer element to render,
|
||||
simulates a click to expand extra badges, and then screenshots only the trade offer element.
|
||||
"""
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# For demonstration purposes, get the first trade offer.
|
||||
# In production, you might want to identify the offer via a URL parameter.
|
||||
|
|
@ -682,3 +678,174 @@ class TradeOfferPNGView(View):
|
|||
browser.close()
|
||||
|
||||
return HttpResponse(png_bytes, content_type="image/png")
|
||||
|
||||
class TradeOfferCreateConfirmView(LoginRequiredMixin, View):
|
||||
"""
|
||||
Processes a two-step create for TradeOffer; on confirmation,
|
||||
commits the offer and shows form errors if any occur.
|
||||
"""
|
||||
def post(self, request, *args, **kwargs):
|
||||
if "confirm" in request.POST:
|
||||
return self._commit_offer(request)
|
||||
elif "edit" in request.POST:
|
||||
return self._redirect_to_edit(request)
|
||||
elif "preview" in request.POST:
|
||||
return self._preview_offer(request)
|
||||
else:
|
||||
return self._preview_offer(request)
|
||||
|
||||
def _commit_offer(self, request):
|
||||
"""
|
||||
Commits the offer after confirmation. Any model ValidationError (for example,
|
||||
due to mismatched card rarities) is caught and added to the form errors so that
|
||||
it shows up in trade_offer_create.html.
|
||||
"""
|
||||
# Instantiate the form with POST data.
|
||||
form = TradeOfferCreateForm(request.POST)
|
||||
# Ensure that the 'initiated_by' queryset is limited to the user's friend codes.
|
||||
form.fields["initiated_by"].queryset = request.user.friend_codes.all()
|
||||
if form.is_valid():
|
||||
try:
|
||||
trade_offer = form.save()
|
||||
except ValidationError as error:
|
||||
form.add_error(None, error)
|
||||
# Update the form's initial data so the template can safely reference have_cards/want_cards.
|
||||
form.initial = {
|
||||
"have_cards": request.POST.getlist("have_cards"),
|
||||
"want_cards": request.POST.getlist("want_cards"),
|
||||
"initiated_by": request.POST.get("initiated_by"),
|
||||
}
|
||||
# Supply additional context required by trade_offer_create.html.
|
||||
from cards.models import Card
|
||||
context = {
|
||||
"form": form,
|
||||
"friend_codes": request.user.friend_codes.all(),
|
||||
"selected_friend_code": (
|
||||
request.user.default_friend_code or request.user.friend_codes.first()
|
||||
),
|
||||
"cards": Card.objects.all().order_by("name", "rarity_level"),
|
||||
}
|
||||
return render(request, "trades/trade_offer_create.html", context)
|
||||
messages.success(request, "Trade offer created successfully!")
|
||||
return HttpResponseRedirect(reverse_lazy("trade_offer_list"))
|
||||
else:
|
||||
# When the form is not valid, update its initial data as well:
|
||||
form.initial = {
|
||||
"have_cards": request.POST.getlist("have_cards"),
|
||||
"want_cards": request.POST.getlist("want_cards"),
|
||||
"initiated_by": request.POST.get("initiated_by"),
|
||||
}
|
||||
from cards.models import Card
|
||||
context = {
|
||||
"form": form,
|
||||
"friend_codes": request.user.friend_codes.all(),
|
||||
"selected_friend_code": (
|
||||
request.user.default_friend_code or request.user.friend_codes.first()
|
||||
),
|
||||
"cards": Card.objects.all().order_by("name", "rarity_level"),
|
||||
}
|
||||
return render(request, "trades/trade_offer_create.html", context)
|
||||
|
||||
def _redirect_to_edit(self, request):
|
||||
query_params = request.POST.copy()
|
||||
query_params.pop("csrfmiddlewaretoken", None)
|
||||
query_params.pop("edit", None)
|
||||
query_params.pop("confirm", None)
|
||||
query_params.pop("preview", None)
|
||||
from django.urls import reverse
|
||||
base_url = reverse("trade_offer_create")
|
||||
url_with_params = f"{base_url}?{query_params.urlencode()}"
|
||||
return HttpResponseRedirect(url_with_params)
|
||||
|
||||
def _preview_offer(self, request):
|
||||
"""
|
||||
Processes the preview action (existing logic remains unchanged).
|
||||
"""
|
||||
form = TradeOfferCreateForm(request.POST)
|
||||
form.fields["initiated_by"].queryset = request.user.friend_codes.all()
|
||||
if not form.is_valid():
|
||||
# Re-render the creation template with errors.
|
||||
return render(request, "trades/trade_offer_create.html", {"form": form})
|
||||
|
||||
# Parse the card selections for "have" and "want" cards.
|
||||
have_selections = self._parse_card_selections("have_cards")
|
||||
want_selections = self._parse_card_selections("want_cards")
|
||||
|
||||
from cards.models import Card
|
||||
|
||||
have_cards_ids = [card_id for card_id, _ in have_selections]
|
||||
cards_have_qs = Card.objects.filter(pk__in=have_cards_ids)
|
||||
cards_have_dict = {card.pk: card for card in cards_have_qs}
|
||||
|
||||
# Define a dummy wrapper for a trade offer card entry.
|
||||
class DummyOfferCard:
|
||||
def __init__(self, card, quantity):
|
||||
self.card = card
|
||||
self.quantity = quantity
|
||||
self.qty_accepted = 0
|
||||
|
||||
have_offer_cards = []
|
||||
for card_id, quantity in have_selections:
|
||||
card = cards_have_dict.get(card_id)
|
||||
if card:
|
||||
have_offer_cards.append(DummyOfferCard(card, quantity))
|
||||
|
||||
want_cards_ids = [card_id for card_id, _ in want_selections]
|
||||
cards_want_qs = Card.objects.filter(pk__in=want_cards_ids)
|
||||
cards_want_dict = {card.pk: card for card in cards_want_qs}
|
||||
want_offer_cards = []
|
||||
for card_id, quantity in want_selections:
|
||||
card = cards_want_dict.get(card_id)
|
||||
if card:
|
||||
want_offer_cards.append(DummyOfferCard(card, quantity))
|
||||
|
||||
# Mimic a related manager's all() method.
|
||||
class DummyManager:
|
||||
def __init__(self, items):
|
||||
self.items = items
|
||||
|
||||
def all(self):
|
||||
return self.items
|
||||
|
||||
# Create a dummy TradeOffer object with properties required by the render_trade_offer tag.
|
||||
class DummyTradeOffer:
|
||||
pass
|
||||
|
||||
dummy_trade_offer = DummyTradeOffer()
|
||||
dummy_trade_offer.pk = 0 # a placeholder primary key
|
||||
dummy_trade_offer.hash = "preview"
|
||||
dummy_trade_offer.rarity_icon = ""
|
||||
dummy_trade_offer.trade_offer_have_cards = DummyManager(have_offer_cards)
|
||||
dummy_trade_offer.trade_offer_want_cards = DummyManager(want_offer_cards)
|
||||
dummy_trade_offer.acceptances = DummyManager([]) # no acceptances in preview
|
||||
dummy_trade_offer.initiated_by = form.cleaned_data["initiated_by"]
|
||||
|
||||
# Pass along the POST data so that hidden inputs can be re-generated.
|
||||
context = {
|
||||
"dummy_trade_offer": dummy_trade_offer,
|
||||
"post_data": request.POST,
|
||||
}
|
||||
return render(request, "trades/trade_offer_confirm_create.html", context)
|
||||
|
||||
def _parse_card_selections(self, key):
|
||||
"""
|
||||
Parses card selections from POST data for a given key (e.g., 'have_cards' or 'want_cards').
|
||||
Selections are expected in the format 'card_id:quantity', defaulting quantity to 1 if missing.
|
||||
Returns a list of (card_id, quantity) tuples.
|
||||
"""
|
||||
selections = self.request.POST.getlist(key)
|
||||
results = []
|
||||
for selection in selections:
|
||||
parts = selection.split(":")
|
||||
try:
|
||||
card_id = int(parts[0])
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
quantity = 1
|
||||
if len(parts) > 1:
|
||||
try:
|
||||
quantity = int(parts[1])
|
||||
except ValueError:
|
||||
pass
|
||||
results.append((card_id, quantity))
|
||||
return results
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue