Various small bug fixes, break out pagination for cards into its own mixin and templatetag
This commit is contained in:
parent
05a279fa3a
commit
138a929da6
17 changed files with 225 additions and 136 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
42
cards/mixins.py
Normal file
42
cards/mixins.py
Normal file
|
|
@ -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
|
||||
11
cards/templatetags/pagination_tags.py
Normal file
11
cards/templatetags/pagination_tags.py
Normal file
|
|
@ -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}
|
||||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{% load card_badge %}
|
||||
{% load pagination_tags %}
|
||||
{% if group_by and groups %}
|
||||
{% for group in groups %}
|
||||
<div class="divider">{{ group.group }}</div>
|
||||
|
|
@ -20,21 +21,7 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
<div class="mt-6">
|
||||
{% if is_paginated %}
|
||||
<div class="flex justify-center space-x-2">
|
||||
{% if page_obj.has_previous %}
|
||||
<button class="btn btn-outline" @click="$dispatch('change-page', { page: {{ page_obj.previous_page_number }} })">
|
||||
Previous
|
||||
</button>
|
||||
{% endif %}
|
||||
<span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
|
||||
{% if page_obj.has_next %}
|
||||
<button class="btn btn-outline" @click="$dispatch('change-page', { page: {{ page_obj.next_page_number }} })">
|
||||
Next
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- Somewhere in your template, e.g., after the card list: -->
|
||||
{% if page_obj %}
|
||||
{% render_pagination page_obj %}
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -18,35 +18,40 @@
|
|||
x-init="loadCards()"
|
||||
x-on:change-page.window="page = $event.detail.page; loadCards()"
|
||||
>
|
||||
<h1 class="text-2xl font-bold mb-4">Cards</h1>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between mb-6">
|
||||
<!-- Sort Dropdown -->
|
||||
<div class="dropdown dropdown-end m-1">
|
||||
<div tabindex="0" class="btn">
|
||||
<span x-text="order === 'absolute' ? 'Absolute' : (order === 'alphabetical' ? 'Alphabetical' : 'Rarity')"></span> 🞃
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li><a href="#" @click.prevent="order = 'absolute'; page = 1; loadCards()">Absolute</a></li>
|
||||
<li><a href="#" @click.prevent="order = 'alphabetical'; page = 1; loadCards()">Alphabetical</a></li>
|
||||
<li><a href="#" @click.prevent="order = 'rarity'; page = 1; loadCards()">Rarity</a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">Cards</h1>
|
||||
</div>
|
||||
<!-- Grouping Dropdown -->
|
||||
<div class="dropdown dropdown-end m-1">
|
||||
<div tabindex="0" class="btn">
|
||||
<span x-text="groupBy === 'none' ? 'No Group' : (groupBy.charAt(0).toUpperCase() + groupBy.slice(1))"></span> 🞃
|
||||
<div>
|
||||
<!-- Sort Dropdown -->
|
||||
<div class="dropdown dropdown-end m-1">
|
||||
<div tabindex="0" class="btn">
|
||||
Sort by: <span x-text="order === 'absolute' ? 'None' : (order === 'alphabetical' ? 'Alphabetical' : 'Rarity')"></span> 🞃
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li><a href="#" @click.prevent="order = 'absolute'; page = 1; loadCards()">None</a></li>
|
||||
<li><a href="#" @click.prevent="order = 'alphabetical'; page = 1; loadCards()">Alphabetical</a></li>
|
||||
<li><a href="#" @click.prevent="order = 'rarity'; page = 1; loadCards()">Rarity</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Grouping Dropdown -->
|
||||
<div class="dropdown dropdown-end m-1">
|
||||
<div tabindex="0" class="btn">
|
||||
Group by: <span x-text="groupBy === 'none' ? 'None' : (groupBy.charAt(0).toUpperCase() + groupBy.slice(1))"></span> 🞃
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li><a href="#" @click.prevent="groupBy = 'none'; page = 1; loadCards()">None</a></li>
|
||||
<li><a href="#" @click.prevent="groupBy = 'deck'; page = 1; loadCards()">Deck</a></li>
|
||||
<li><a href="#" @click.prevent="groupBy = 'cardset'; page = 1; loadCards()">Cardset</a></li>
|
||||
<li><a href="#" @click.prevent="groupBy = 'rarity'; page = 1; loadCards()">Rarity</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul tabindex="0" class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li><a href="#" @click.prevent="groupBy = 'none'; page = 1; loadCards()">No Group</a></li>
|
||||
<li><a href="#" @click.prevent="groupBy = 'deck'; page = 1; loadCards()">Deck</a></li>
|
||||
<li><a href="#" @click.prevent="groupBy = 'cardset'; page = 1; loadCards()">Cardset</a></li>
|
||||
<li><a href="#" @click.prevent="groupBy = 'rarity'; page = 1; loadCards()">Rarity</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Container for the partial card list -->
|
||||
<div x-ref="cardList">
|
||||
<!-- The contents of _card_list.html will be loaded here via AJAX -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -1,21 +1,8 @@
|
|||
{% load card_badge %}
|
||||
{% comment %}
|
||||
This partial expects:
|
||||
- cards: a list of card objects
|
||||
- mode: a string that determines the render style.
|
||||
It should be "offered" for Most Offered Cards and "wanted" for Most Wanted Cards.
|
||||
- Optional:
|
||||
'show_zero' flag (default False): if True, also display cards with 0 offers.
|
||||
'layout' variable: if set to "auto", use an auto-fit grid based on available horizontal space.
|
||||
{% endcomment %}
|
||||
{% if cards %}
|
||||
<div class="mx-4 grid gap-3 grid-cols-[repeat(auto-fit,minmax(150px,1fr))] justify-items-center">
|
||||
{% for card in cards %}
|
||||
{% if mode == "offered" %}
|
||||
<a href="{% url 'cards:card_detail' card.id %}"
|
||||
{% else %}
|
||||
<a href="{% url 'cards:card_detail' card.id %}"
|
||||
{% endif %}
|
||||
class="flex justify-between items-center text-primary no-underline">
|
||||
{% card_badge card card.offer_count %}
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
</div>
|
||||
<div class="card-body my-4 p-0">
|
||||
{% cache 3600 most_offered_cards %}
|
||||
{% include "home/_card_list.html" with cards=most_offered_cards mode="wanted" %}
|
||||
{% include "home/_card_list.html" with cards=most_offered_cards %}
|
||||
{% endcache %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
</div>
|
||||
<div class="card-body my-4 p-0">
|
||||
{% cache 3600 most_wanted_cards %}
|
||||
{% include "home/_card_list.html" with cards=most_wanted_cards mode="offered" %}
|
||||
{% include "home/_card_list.html" with cards=most_wanted_cards %}
|
||||
{% endcache %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -74,7 +74,7 @@
|
|||
</div>
|
||||
<div class="card-body my-4 p-0">
|
||||
{% cache 3600 least_offered_cards %}
|
||||
{% include "home/_card_list.html" with cards=least_offered_cards mode="wanted" %}
|
||||
{% include "home/_card_list.html" with cards=least_offered_cards %}
|
||||
{% endcache %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -121,8 +121,8 @@
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- DaisyUI Tabs for Featured Offers -->
|
||||
<div class="card card-border bg-base-100 shadow-lg w-96 md:w-80 lg:w-96 mt-8 mx-auto">
|
||||
<!-- DaisyUI Tabs for Featured Offers (hidden for now) -->
|
||||
<div class="card card-border bg-base-100 shadow-lg w-96 md:w-80 lg:w-96 mt-8 mx-auto hidden">
|
||||
<!-- Tabs navigation using daisyUI tabs-box -->
|
||||
<div class="tabs tabs-box bg-white dark:bg-base-100 grid grid-cols-3 gap-1.5 justify-items-stretch">
|
||||
<!-- Radio inputs for controlling tab state -->
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
<div class="container mx-auto max-w-4xl mt-6" x-data="{ allExpanded: false }">
|
||||
<!-- Header-->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<a href="{% url 'trade_offer_create' %}" class="btn btn-success">Create New Offer</a>
|
||||
<h1 class="text-2xl font-bold">All Trade Offers</h1>
|
||||
<div>
|
||||
<form method="get" class="flex items-center space-x-4" x-data>
|
||||
<label class="cursor-pointer flex items-center space-x-2">
|
||||
<form method="get" class="flex items-center gap-4" x-data>
|
||||
<label class="cursor-pointer flex items-center gap-2">
|
||||
<span x-text="allExpanded ? 'Collapse All' : 'Expand All'"></span>
|
||||
<input type="checkbox" name="all_expanded" value="true" class="toggle toggle-primary" @click="allExpanded = !allExpanded; $dispatch('toggle-all', { expanded: allExpanded })">
|
||||
</label>
|
||||
<label class="cursor-pointer flex items-center space-x-2">
|
||||
<label class="cursor-pointer flex items-center gap-2">
|
||||
<span>Only Closed</span>
|
||||
<input type="checkbox" name="show_closed" value="true" class="toggle toggle-primary" @change="$el.form.submit()" {% if show_closed %}checked{% endif %}>
|
||||
</label>
|
||||
|
|
@ -24,7 +24,9 @@
|
|||
</div>
|
||||
<!-- Trade Offers -->
|
||||
<section class="mb-12">
|
||||
<h2 class="text-2xl font-bold mb-4">All Trade Offers</h2>
|
||||
<div class="flex justify-end mb-4">
|
||||
<a href="{% url 'trade_offer_create' %}" class="btn btn-success">Create New Offer</a>
|
||||
</div>
|
||||
<div
|
||||
id="all-trade-offers"
|
||||
x-data="tradeOffersPagination('{% url 'trade_offer_list' %}?')"
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@
|
|||
<div class="rarity row-start-2 col-span-2 truncate self-end align-bottom text-xs">{{ rarity }}</div>
|
||||
<div class="cardset row-start-2 col-start-3 col-span-2 text-right truncate self-end align-bottom font-semibold leading-none text-sm">{{ cardset }}</div>
|
||||
</div>
|
||||
{% if quantity %}<span class="card-quantity-badge freeze-bg-color absolute top-3.75 right-1.5 bg-gray-600 text-white text-sm font-semibold rounded-full px-1.5">{{ quantity }}</span>{% endif %}
|
||||
{% if quantity != "" %}<span class="card-quantity-badge freeze-bg-color absolute top-3.75 right-1.5 bg-gray-600 text-white text-sm font-semibold rounded-full px-1.5">{{ quantity }}</span>{% endif %}
|
||||
</div>
|
||||
38
theme/templatetags/pagination_controls.html
Normal file
38
theme/templatetags/pagination_controls.html
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<div class="flex justify-center items-center space-x-2">
|
||||
<!-- First Button -->
|
||||
<button class="btn btn-outline btn-md"
|
||||
{% if not page_obj.has_previous %}disabled{% endif %}
|
||||
@click="$dispatch('change-page', { page: 1 })">
|
||||
First
|
||||
</button>
|
||||
<!-- Previous Button -->
|
||||
<button class="btn btn-outline btn-md"
|
||||
{% if not page_obj.has_previous %}disabled{% endif %}
|
||||
@click="$dispatch('change-page', { page: {{ page_obj.previous_page }} })">
|
||||
Prev
|
||||
</button>
|
||||
<!-- Goto Page -->
|
||||
<span class="flex items-center space-x-1 gap-2">
|
||||
<input type="number" min="1" max="{{ page_obj.paginator.num_pages }}" value="{{ page_obj.number }}"
|
||||
class="input input-xs text-center" id="gotoPageInput">
|
||||
<button class="btn btn-outline btn-md"
|
||||
@click="(document.getElementById('gotoPageInput').value >= 1 && document.getElementById('gotoPageInput').value <= {{ page_obj.paginator.num_pages }}) && $dispatch('change-page', { page: parseInt(document.getElementById('gotoPageInput').value) })">
|
||||
Go
|
||||
</button>
|
||||
</span>
|
||||
<!-- Next Button -->
|
||||
<button class="btn btn-outline btn-md"
|
||||
{% if not page_obj.has_next %}disabled{% endif %}
|
||||
@click="$dispatch('change-page', { page: {{ page_obj.next_page }} })">
|
||||
Next
|
||||
</button>
|
||||
<!-- Last Button -->
|
||||
<button class="btn btn-outline btn-md"
|
||||
{% if page_obj.number == page_obj.paginator.num_pages %}disabled{% endif %}
|
||||
@click="$dispatch('change-page', { page: {{ page_obj.paginator.num_pages }} })">
|
||||
Last
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-center mt-2">
|
||||
<span>Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
|
||||
</div>
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
<!-- Main Trade Offer Row -->
|
||||
<div class="flip-face-body self-start">
|
||||
<a href="{% url 'trade_offer_detail' pk=offer_pk %}" class="no-underline block">
|
||||
<div class="px-2 main-badges {% if not screenshot_mode and have_cards_available|length == 1 and want_cards_available|length == 1 %}py-[14px]{%else%}pb-0{% endif %}">
|
||||
<div class="px-2 main-badges pb-0">
|
||||
{% if screenshot_mode and num_cards_available >= 4 %}
|
||||
<!-- When screenshot_mode is true, use an outer grid with 3 columns: Has side, a vertical divider, and Wants side -->
|
||||
<div class="flex flex-row gap-2 justify-around">
|
||||
|
|
@ -151,6 +151,8 @@
|
|||
d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="h-5"></div>
|
||||
{% endif %}
|
||||
{% if not screenshot_mode %}
|
||||
<div class="flip-face-footer self-end">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue