From 138a929da6c0e54415c03ddefbc8e31030af9146 Mon Sep 17 00:00:00 2001 From: badbl0cks <4161747+badbl0cks@users.noreply.github.com> Date: Sat, 29 Mar 2025 00:27:40 -0700 Subject: [PATCH] Various small bug fixes, break out pagination for cards into its own mixin and templatetag --- accounts/migrations/0001_initial.py | 2 +- cards/migrations/0001_initial.py | 2 +- cards/mixins.py | 42 ++++++++++++ cards/templatetags/pagination_tags.py | 11 ++++ cards/views.py | 60 ++++++++--------- home/views.py | 5 +- seed/0003_Cards.json | 66 +++++++++---------- seed/0004_TestUsers.json | 20 ++++++ theme/templates/cards/_card_list.html | 23 ++----- theme/templates/cards/card_list.html | 49 +++++++------- theme/templates/home/_card_list.html | 13 ---- theme/templates/home/home.html | 10 +-- .../trades/trade_offer_all_list.html | 12 ++-- theme/templatetags/card_badge.html | 2 +- theme/templatetags/pagination_controls.html | 38 +++++++++++ theme/templatetags/trade_offer.html | 4 +- trades/migrations/0001_initial.py | 2 +- 17 files changed, 225 insertions(+), 136 deletions(-) create mode 100644 cards/mixins.py create mode 100644 cards/templatetags/pagination_tags.py create mode 100644 theme/templatetags/pagination_controls.html diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py index 142bab2..76a3141 100644 --- a/accounts/migrations/0001_initial.py +++ b/accounts/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.2 on 2025-03-28 04:43 +# Generated by Django 5.1.2 on 2025-03-29 03:33 import accounts.models import django.contrib.auth.models diff --git a/cards/migrations/0001_initial.py b/cards/migrations/0001_initial.py index 8890d79..90b83d8 100644 --- a/cards/migrations/0001_initial.py +++ b/cards/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.2 on 2025-03-28 04:43 +# Generated by Django 5.1.2 on 2025-03-29 03:33 import django.db.models.deletion from django.db import migrations, models diff --git a/cards/mixins.py b/cards/mixins.py new file mode 100644 index 0000000..575d62c --- /dev/null +++ b/cards/mixins.py @@ -0,0 +1,42 @@ +from math import ceil + +class ReusablePaginationMixin: + """ + A mixin that encapsulates reusable pagination logic. + Use in Django ListViews to generate custom pagination context. + """ + per_page = 10 # Default; can be overridden in your view. + + def paginate_data(self, data_list, page_number): + """ + Paginate a list of items. + + Arguments: + data_list (list): The list of items to paginate. + page_number (int): Current page number. + + Returns: + tuple: (paginated_items, pagination_context) + """ + total_items = len(data_list) + num_pages = ceil(total_items / self.per_page) if self.per_page > 0 else 1 + + # Ensure page_number is within valid bounds. + if page_number < 1: + page_number = 1 + elif page_number > num_pages: + page_number = num_pages + + start = (page_number - 1) * self.per_page + end = page_number * self.per_page + items = data_list[start:end] + + pagination_context = { + "number": page_number, + "has_previous": page_number > 1, + "has_next": page_number < num_pages, + "previous_page": page_number - 1 if page_number > 1 else 1, + "next_page": page_number + 1 if page_number < num_pages else num_pages, + "paginator": {"num_pages": num_pages}, + } + return items, pagination_context \ No newline at end of file diff --git a/cards/templatetags/pagination_tags.py b/cards/templatetags/pagination_tags.py new file mode 100644 index 0000000..a2d5ee0 --- /dev/null +++ b/cards/templatetags/pagination_tags.py @@ -0,0 +1,11 @@ +from django import template + +register = template.Library() + +@register.inclusion_tag("templatetags/pagination_controls.html", takes_context=True) +def render_pagination(context, page_obj): + """ + Renders pagination controls given a page_obj. + The controls use values like page_obj.number, page_obj.has_previous, etc. + """ + return {"page_obj": page_obj} \ No newline at end of file diff --git a/cards/views.py b/cards/views.py index 9f92e32..cf5dc50 100644 --- a/cards/views.py +++ b/cards/views.py @@ -3,6 +3,7 @@ from django.urls import reverse_lazy from django.views.generic import UpdateView, DeleteView, CreateView, ListView, DetailView from cards.models import Card from trades.models import TradeOffer +from cards.mixins import ReusablePaginationMixin class CardDetailView(DetailView): model = Card @@ -62,9 +63,9 @@ class TradeOfferWantCardListView(ListView): context['side'] = 'want' return context -class CardListView(ListView): +class CardListView(ReusablePaginationMixin, ListView): model = Card - paginate_by = 100 # For non-grouped mode; grouping mode will override default pagination. + paginate_by = 36 # For non-grouped mode; grouping mode will override default pagination. context_object_name = "cards" def get_template_names(self): @@ -102,13 +103,11 @@ class CardListView(ListView): context["group_by"] = group_by if group_by in ("deck", "cardset", "rarity"): - # Fetch the complete queryset (no slicing) full_qs = self.get_queryset() all_cards = list(full_qs) flat_cards = [] if group_by == "deck": - # Each card may belong to multiple decks – reproduce the existing logic. for card in all_cards: for deck in card.decks.all(): flat_cards.append({"group": deck.name, "card": card}) @@ -119,23 +118,19 @@ class CardListView(ListView): flat_cards.sort(key=lambda x: x["group"].lower()) elif group_by == "rarity": for card in all_cards: - flat_cards.append({"group": card.rarity_level, "card": card}) - flat_cards.sort(key=lambda x: x["group"], reverse=True) + flat_cards.append({"group": card.rarity_icon, "sort_group": card.rarity_level, "card": card}) + flat_cards.sort(key=lambda x: x["sort_group"], reverse=True) - total_cards = len(flat_cards) try: page_number = int(self.request.GET.get("page", 1)) - if page_number < 1: - page_number = 1 except ValueError: page_number = 1 - per_page = 96 - start = (page_number - 1) * per_page - end = page_number * per_page - page_flat_cards = flat_cards[start:end] + # Use our custom mixin logic here + self.per_page = 36 + page_flat_cards, pagination_context = self.paginate_data(flat_cards, page_number) - # Reassemble the flat list into grouped structure for just this page. + # Reassemble the flat list into groups for the current page. page_groups = [] for item in page_flat_cards: group_value = item["group"] @@ -145,25 +140,22 @@ class CardListView(ListView): else: page_groups.append({"group": group_value, "cards": [card_obj]}) context["groups"] = page_groups - - # Set up custom pagination context. - from math import ceil - num_pages = ceil(total_cards / per_page) - page_obj = { - "number": page_number, - "has_previous": page_number > 1, - "has_next": page_number < num_pages, - "previous_page_number": page_number - 1 if page_number > 1 else None, - "next_page_number": page_number + 1 if page_number < num_pages else None, - "paginator": { - "num_pages": num_pages, - }, - } - context["page_obj"] = page_obj - context["is_paginated"] = total_cards > per_page - context["total_cards"] = total_cards - # Optionally, keep the full queryset in object_list. + context["page_obj"] = pagination_context + context["total_cards"] = len(flat_cards) context["object_list"] = full_qs return context - - return context + else: + # For non-grouped mode, transform the built-in paginator page + if "page_obj" in context: + page = context["page_obj"] + # Create a unified pagination context dict + custom_page_obj = { + "number": page.number, + "has_previous": page.has_previous(), + "has_next": page.has_next(), + "previous_page": page.previous_page_number() if page.has_previous() else 1, + "next_page": page.next_page_number() if page.has_next() else page.paginator.num_pages, + "paginator": {"num_pages": page.paginator.num_pages}, + } + context["page_obj"] = custom_page_obj + return context \ No newline at end of file diff --git a/home/views.py b/home/views.py index 237d30d..db17386 100644 --- a/home/views.py +++ b/home/views.py @@ -2,6 +2,7 @@ from collections import defaultdict, OrderedDict from django.views.generic import TemplateView from django.urls import reverse_lazy from django.db.models import Count, Q, Prefetch, Sum, F, IntegerField, Value, BooleanField, Case, When +from django.db.models.functions import Coalesce from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from trades.models import TradeOffer, TradeAcceptance, TradeOfferHaveCard, TradeOfferWantCard from cards.models import Card @@ -61,7 +62,9 @@ class HomePageView(TemplateView): # Least Offered Cards try: context["least_offered_cards"] = ( - Card.objects.annotate(offer_count=Sum("tradeofferhavecard__quantity")) + Card.objects.annotate( + offer_count=Coalesce(Sum("tradeofferhavecard__quantity"), 0) + ) .order_by("offer_count")[:6] ) except Exception as e: diff --git a/seed/0003_Cards.json b/seed/0003_Cards.json index a930810..df7f1ac 100644 --- a/seed/0003_Cards.json +++ b/seed/0003_Cards.json @@ -4463,7 +4463,7 @@ "decks": [ 3 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -4479,7 +4479,7 @@ "decks": [ 2 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -4495,7 +4495,7 @@ "decks": [ 4 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -4511,7 +4511,7 @@ "decks": [ 2 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -4527,7 +4527,7 @@ "decks": [ 3 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -4543,7 +4543,7 @@ "decks": [ 4 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -4559,7 +4559,7 @@ "decks": [ 3 ], - "rarity_icon": "🌟🌟🌟", + "rarity_icon": "⭐️⭐️⭐️", "rarity_level": 7, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -4575,7 +4575,7 @@ "decks": [ 4 ], - "rarity_icon": "🌟🌟🌟", + "rarity_icon": "⭐️⭐️⭐️", "rarity_level": 7, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -4591,7 +4591,7 @@ "decks": [ 2 ], - "rarity_icon": "🌟🌟🌟", + "rarity_icon": "⭐️⭐️⭐️", "rarity_level": 7, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -4609,7 +4609,7 @@ 3, 4 ], - "rarity_icon": "🌟🌟🌟", + "rarity_icon": "⭐️⭐️⭐️", "rarity_level": 7, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -5991,7 +5991,7 @@ "decks": [ 5 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -6007,7 +6007,7 @@ "decks": [ 5 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -6023,7 +6023,7 @@ "decks": [ 5 ], - "rarity_icon": "🌟🌟🌟", + "rarity_icon": "⭐️⭐️⭐️", "rarity_level": 7, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9218,7 +9218,7 @@ "decks": [ 6 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9234,7 +9234,7 @@ "decks": [ 7 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9250,7 +9250,7 @@ "decks": [ 6 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9266,7 +9266,7 @@ "decks": [ 7 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9282,7 +9282,7 @@ "decks": [ 6 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9298,7 +9298,7 @@ "decks": [ 7 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9314,7 +9314,7 @@ "decks": [ 6 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9330,7 +9330,7 @@ "decks": [ 7 ], - "rarity_icon": "🌟🌟", + "rarity_icon": "⭐️⭐️", "rarity_level": 6, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9346,7 +9346,7 @@ "decks": [ 7 ], - "rarity_icon": "🌟🌟🌟", + "rarity_icon": "⭐️⭐️⭐️", "rarity_level": 7, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -9362,7 +9362,7 @@ "decks": [ 6 ], - "rarity_icon": "🌟🌟🌟", + "rarity_icon": "⭐️⭐️⭐️", "rarity_level": 7, "created_at": "2025-02-17T02:44:18.706Z", "updated_at": "2025-02-17T02:44:18.706Z" @@ -12484,7 +12484,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" @@ -12500,7 +12500,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" @@ -12516,7 +12516,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" @@ -12532,7 +12532,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" @@ -12548,7 +12548,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" @@ -12564,7 +12564,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" @@ -12580,7 +12580,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" @@ -12596,7 +12596,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" @@ -12612,7 +12612,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" @@ -12628,7 +12628,7 @@ "decks": [ 9 ], - "rarity_level": 6, + "rarity_level": 5, "rarity_icon": "✨", "created_at": "2025-03-26T12:25:17.706Z", "updated_at": "2025-03-26T12:25:17.706Z" diff --git a/seed/0004_TestUsers.json b/seed/0004_TestUsers.json index 66e4853..66637c9 100644 --- a/seed/0004_TestUsers.json +++ b/seed/0004_TestUsers.json @@ -58,5 +58,25 @@ "created_at": "2025-03-13T04:52:29.166Z", "updated_at": "2025-03-13T04:52:29.166Z" } +}, +{ + "model": "account.emailaddress", + "pk": 1, + "fields": { + "user": 1, + "email": "rob@badblocks.email", + "verified": true, + "primary": true + } +}, +{ + "model": "account.emailaddress", + "pk": 2, + "fields": { + "user": 2, + "email": "nathanward2016@gmail.com", + "verified": true, + "primary": true + } } ] diff --git a/theme/templates/cards/_card_list.html b/theme/templates/cards/_card_list.html index b6de111..7336637 100644 --- a/theme/templates/cards/_card_list.html +++ b/theme/templates/cards/_card_list.html @@ -1,4 +1,5 @@ {% load card_badge %} +{% load pagination_tags %} {% if group_by and groups %} {% for group in groups %}