From b20ca8a88815e3843135c75934a4b00bb79b719e Mon Sep 17 00:00:00 2001 From: badbl0cks <4161747+badbl0cks@users.noreply.github.com> Date: Thu, 13 Mar 2025 21:03:41 -0700 Subject: [PATCH] Fix pagination for trade offer lists --- theme/templates/trades/_trade_offer_list.html | 40 +----- .../trades/_trade_offer_list_paginated.html | 38 +++++ .../trades/trade_offer_all_list.html | 59 +++++--- .../templates/trades/trade_offer_my_list.html | 108 +++++++------- trades/views.py | 135 +++++++++++++----- 5 files changed, 233 insertions(+), 147 deletions(-) create mode 100644 theme/templates/trades/_trade_offer_list_paginated.html diff --git a/theme/templates/trades/_trade_offer_list.html b/theme/templates/trades/_trade_offer_list.html index 8bfc930..980be3b 100644 --- a/theme/templates/trades/_trade_offer_list.html +++ b/theme/templates/trades/_trade_offer_list.html @@ -17,42 +17,4 @@ {% empty %}
No trade offers available.
{% endfor %} - - -{% if offers.has_other_pages %} - -{% endif %} \ No newline at end of file + \ No newline at end of file diff --git a/theme/templates/trades/_trade_offer_list_paginated.html b/theme/templates/trades/_trade_offer_list_paginated.html new file mode 100644 index 0000000..f3ff479 --- /dev/null +++ b/theme/templates/trades/_trade_offer_list_paginated.html @@ -0,0 +1,38 @@ +{% include "trades/_trade_offer_list.html" %} +{% if offers.has_other_pages %} + +{% endif %} \ No newline at end of file diff --git a/theme/templates/trades/trade_offer_all_list.html b/theme/templates/trades/trade_offer_all_list.html index 31b4807..1dd2860 100644 --- a/theme/templates/trades/trade_offer_all_list.html +++ b/theme/templates/trades/trade_offer_all_list.html @@ -25,24 +25,47 @@

All Trade Offers

- {% if all_trade_offers_paginated.object_list %} - {% include "trades/_trade_offer_list.html" with offers=all_trade_offers_paginated %} -
- {% if all_trade_offers_paginated.has_previous %} - Previous - {% else %} - - {% endif %} - Page {{ all_trade_offers_paginated.number }} of {{ all_trade_offers_paginated.paginator.num_pages }} - {% if all_trade_offers_paginated.has_next %} - Next - {% else %} - - {% endif %} -
- {% else %} -

No trade offers found.

- {% endif %} +
+ {% include "trades/_trade_offer_list_paginated.html" with offers=all_trade_offers_paginated %} +
+ + {% endblock content %} \ No newline at end of file diff --git a/theme/templates/trades/trade_offer_my_list.html b/theme/templates/trades/trade_offer_my_list.html index ea8b14f..9c322e3 100644 --- a/theme/templates/trades/trade_offer_my_list.html +++ b/theme/templates/trades/trade_offer_my_list.html @@ -30,70 +30,70 @@

Waiting for Your Response

- {% if trade_acceptances_waiting_paginated.object_list %} - {% include "trades/_trade_offer_list.html" with offers=trade_acceptances_waiting_paginated %} -
- {% if trade_acceptances_waiting_paginated.has_previous %} - Previous - {% else %} - - {% endif %} - Page {{ trade_acceptances_waiting_paginated.number }} of {{ trade_acceptances_waiting_paginated.paginator.num_pages }} - {% if trade_acceptances_waiting_paginated.has_next %} - Next - {% else %} - - {% endif %} -
- {% else %} -

None at this time.

- {% endif %} +
+ {% include "trades/_trade_offer_list_paginated.html" with offers=trade_acceptances_waiting_paginated %} +

Waiting for Their Response

- {% if other_party_trade_acceptances_paginated.object_list %} - {% include "trades/_trade_offer_list.html" with offers=other_party_trade_acceptances_paginated %} -
- {% if other_party_trade_acceptances_paginated.has_previous %} - Previous - {% else %} - - {% endif %} - Page {{ other_party_trade_acceptances_paginated.number }} of {{ other_party_trade_acceptances_paginated.paginator.num_pages }} - {% if other_party_trade_acceptances_paginated.has_next %} - Next - {% else %} - - {% endif %} -
- {% else %} -

None at this time.

- {% endif %} +
+ {% include "trades/_trade_offer_list_paginated.html" with offers=other_party_trade_acceptances_paginated %} +

My Trade Offers

- {% if my_trade_offers_paginated.object_list %} - {% include "trades/_trade_offer_list.html" with offers=my_trade_offers_paginated %} -
- {% if my_trade_offers_paginated.has_previous %} - Previous - {% else %} - - {% endif %} - Page {{ my_trade_offers_paginated.number }} of {{ my_trade_offers_paginated.paginator.num_pages }} - {% if my_trade_offers_paginated.has_next %} - Next - {% else %} - - {% endif %} -
- {% else %} -

No trade offers found.

- {% endif %} +
+ {% include "trades/_trade_offer_list_paginated.html" with offers=my_trade_offers_paginated %} +
+ + {% endblock content %} \ No newline at end of file diff --git a/trades/views.py b/trades/views.py index d0bdb84..2ae145b 100644 --- a/trades/views.py +++ b/trades/views.py @@ -72,13 +72,13 @@ class TradeOfferAllListView(ListView): model = TradeOffer template_name = "trades/trade_offer_all_list.html" - def get_context_data(self, *, object_list=None, **kwargs): context = super().get_context_data(**kwargs) request = self.request show_closed = request.GET.get("show_closed", "false").lower() == "true" context["show_closed"] = show_closed + # Build the queryset with our related objects. queryset = ( TradeOffer.objects.select_related('initiated_by') .prefetch_related( @@ -96,12 +96,42 @@ class TradeOfferAllListView(ListView): else: queryset = queryset.filter(is_closed=False) + # On initial load, use the 'offers_page' parameter. offers_page = request.GET.get("offers_page") offers_paginator = Paginator(queryset, 10) context["all_trade_offers_paginated"] = offers_paginator.get_page(offers_page) - return context + 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": + page = self.request.GET.get("page") + show_closed = self.request.GET.get("show_closed", "false").lower() == "true" + + queryset = ( + TradeOffer.objects.select_related('initiated_by') + .prefetch_related( + 'trade_offer_have_cards__card', + 'trade_offer_want_cards__card', + Prefetch( + 'acceptances', + queryset=TradeAcceptance.objects.select_related('accepted_by', 'requested_card', 'offered_card') + ) + ) + .order_by("-updated_at") + ) + if show_closed: + queryset = queryset.filter(is_closed=True) + else: + queryset = queryset.filter(is_closed=False) + paginated_offers = Paginator(queryset, 10).get_page(page) + return render( + self.request, + "trades/_trade_offer_list_paginated.html", + {"offers": paginated_offers} + ) + return super().render_to_response(context, **response_kwargs) + class TradeOfferMyListView(LoginRequiredMixin, ListView): model = TradeOffer # Fallback model; our context data holds separate filtered querysets. template_name = "trades/trade_offer_my_list.html" @@ -125,40 +155,35 @@ class TradeOfferMyListView(LoginRequiredMixin, ListView): .order_by("-updated_at") ) - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - request = self.request - show_closed = request.GET.get("show_closed", "false").lower() == "true" - context["show_closed"] = show_closed - - friend_codes = request.user.friend_codes.all() - friend_code_param = request.GET.get("friend_code") + def get_selected_friend_code(self): + friend_codes = self.request.user.friend_codes.all() + friend_code_param = self.request.GET.get("friend_code") if friend_code_param: try: selected_friend_code = friend_codes.get(pk=friend_code_param) except friend_codes.model.DoesNotExist: - selected_friend_code = request.user.default_friend_code or friend_codes.first() + selected_friend_code = self.request.user.default_friend_code or friend_codes.first() else: - selected_friend_code = request.user.default_friend_code or friend_codes.first() + selected_friend_code = self.request.user.default_friend_code or friend_codes.first() if not selected_friend_code: raise PermissionDenied("You do not have an active friend code associated with your account.") - context["selected_friend_code"] = selected_friend_code - context["friend_codes"] = friend_codes + return selected_friend_code + def get_show_closed(self): + return self.request.GET.get("show_closed", "false").lower() == "true" + + def get_my_trade_offers_paginated(self, page_param): + selected_friend_code = self.get_selected_friend_code() queryset = self.get_queryset().filter(initiated_by=selected_friend_code) - if show_closed: + if self.get_show_closed(): queryset = queryset.filter(is_closed=True) else: queryset = queryset.filter(is_closed=False) + return Paginator(queryset, 10).get_page(page_param) - offers_page = request.GET.get("offers_page") - offers_paginator = Paginator(queryset, 10) - context["my_trade_offers_paginated"] = offers_paginator.get_page(offers_page) - - # ----- Trade Acceptances involving the user ----- - # Update terminal states to include the thanked and rejected states. + def get_involved_acceptances(self, selected_friend_code): terminal_states = [ TradeAcceptance.AcceptanceState.THANKED_BY_INITIATOR, TradeAcceptance.AcceptanceState.THANKED_BY_ACCEPTOR, @@ -169,34 +194,72 @@ class TradeOfferMyListView(LoginRequiredMixin, ListView): involved_acceptances_qs = TradeAcceptance.objects.filter( Q(trade_offer__initiated_by=selected_friend_code) | Q(accepted_by=selected_friend_code) ).order_by("-updated_at") + if self.get_show_closed(): + return involved_acceptances_qs.filter(state__in=terminal_states) + return involved_acceptances_qs.exclude(state__in=terminal_states) - if show_closed: - involved_acceptances = involved_acceptances_qs.filter(state__in=terminal_states) - else: - involved_acceptances = involved_acceptances_qs.exclude(state__in=terminal_states) - - # ----- Split Acceptances into "Waiting for Your Response" and "Other" ----- + def get_trade_acceptances_waiting_paginated(self, page_param): + selected_friend_code = self.get_selected_friend_code() + involved_acceptances = self.get_involved_acceptances(selected_friend_code) waiting_acceptances = involved_acceptances.filter( Q(trade_offer__initiated_by=selected_friend_code, state__in=[ TradeAcceptance.AcceptanceState.ACCEPTED, TradeAcceptance.AcceptanceState.RECEIVED, ]) | - Q(accepted_by=selected_friend_code, state__in=[ - TradeAcceptance.AcceptanceState.SENT - ]) + Q(accepted_by=selected_friend_code, state__in=[TradeAcceptance.AcceptanceState.SENT]) ) - other_party_trade_acceptances = involved_acceptances.exclude(pk__in=waiting_acceptances.values("pk")) + return Paginator(waiting_acceptances, 10).get_page(page_param) + def get_other_party_trade_acceptances_paginated(self, page_param): + selected_friend_code = self.get_selected_friend_code() + involved_acceptances = self.get_involved_acceptances(selected_friend_code) + waiting_acceptances = involved_acceptances.filter( + Q(trade_offer__initiated_by=selected_friend_code, state__in=[ + TradeAcceptance.AcceptanceState.ACCEPTED, + TradeAcceptance.AcceptanceState.RECEIVED, + ]) | + Q(accepted_by=selected_friend_code, state__in=[TradeAcceptance.AcceptanceState.SENT]) + ) + other_acceptances = involved_acceptances.exclude(pk__in=waiting_acceptances.values("pk")) + return Paginator(other_acceptances, 10).get_page(page_param) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + request = self.request + show_closed = self.get_show_closed() + context["show_closed"] = show_closed + + selected_friend_code = self.get_selected_friend_code() + context["selected_friend_code"] = selected_friend_code + context["friend_codes"] = request.user.friend_codes.all() + + # Use request params for initial full page load (could be None) + offers_page = request.GET.get("offers_page") waiting_page = request.GET.get("waiting_page") other_page = request.GET.get("other_page") - waiting_paginator = Paginator(waiting_acceptances, 10) - other_party_paginator = Paginator(other_party_trade_acceptances, 10) - - context["trade_acceptances_waiting_paginated"] = waiting_paginator.get_page(waiting_page) - context["other_party_trade_acceptances_paginated"] = other_party_paginator.get_page(other_page) + context["my_trade_offers_paginated"] = self.get_my_trade_offers_paginated(offers_page) + context["trade_acceptances_waiting_paginated"] = self.get_trade_acceptances_waiting_paginated(waiting_page) + context["other_party_trade_acceptances_paginated"] = self.get_other_party_trade_acceptances_paginated(other_page) return context + 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": + ajax_section = self.request.GET.get("ajax_section") + page = self.request.GET.get("page") + if ajax_section == "my_trade_offers": + offers = self.get_my_trade_offers_paginated(page) + elif ajax_section == "waiting_acceptances": + offers = self.get_trade_acceptances_waiting_paginated(page) + elif ajax_section == "other_party_acceptances": + offers = self.get_other_party_trade_acceptances_paginated(page) + else: + # Fallback to my trade offers. + offers = self.get_my_trade_offers_paginated(page) + return render(self.request, "trades/_trade_offer_list_paginated.html", {"offers": offers}) + return super().render_to_response(context, **response_kwargs) + class TradeOfferDeleteView(LoginRequiredMixin, DeleteView): model = TradeOffer success_url = reverse_lazy("trade_offer_list")