From 4e50e1545c90ffb678803fa3eb582d2593ac75ef Mon Sep 17 00:00:00 2001 From: badbl0cks <4161747+badbl0cks@users.noreply.github.com> Date: Tue, 29 Apr 2025 13:50:52 -0700 Subject: [PATCH] Refactor card badge and multiselect template tags to properly implement and/or improve caching and context handling - Updated `card_badge` and `card_multiselect` template tags to utilize `reverse_lazy` for URL resolution. - Enhanced caching mechanisms in `card_badge.html` and `card_multiselect.html` to improve performance. - Introduced a new template `_card_multiselect_options.html` for rendering multiselect options. - Improved context management in `card_multiselect` to handle selected cards and dynamic placeholders. - Added error handling for query hashing in `card_multiselect` to ensure robustness. - Updated `trade_offer_tags` to optimize database queries using `select_related` for related objects. --- cards/templatetags/card_badge.py | 35 +++--- cards/templatetags/card_multiselect.py | 98 ++++++++++------- .../_card_multiselect_options.html | 30 ++++++ theme/templatetags/card_badge.html | 55 +++++----- theme/templatetags/card_multiselect.html | 24 ++--- theme/templatetags/trade_acceptance.html | 102 +++++++++--------- theme/templatetags/trade_offer.html | 2 +- trades/apps.py | 3 +- trades/signals.py | 22 +++- trades/templatetags/trade_offer_tags.py | 26 +++-- 10 files changed, 234 insertions(+), 163 deletions(-) create mode 100644 theme/templatetags/_card_multiselect_options.html diff --git a/cards/templatetags/card_badge.py b/cards/templatetags/card_badge.py index b69bdb7..ace3b17 100644 --- a/cards/templatetags/card_badge.py +++ b/cards/templatetags/card_badge.py @@ -1,41 +1,46 @@ from django import template +from django.conf import settings from django.template.loader import render_to_string from django.utils.safestring import mark_safe -from django.urls import reverse +from django.urls import reverse_lazy register = template.Library() -@register.inclusion_tag("templatetags/card_badge.html") -def card_badge(card, quantity=None, expanded=False): - url = reverse('cards:card_detail', args=[card.pk]) - return { +@register.inclusion_tag("templatetags/card_badge.html", takes_context=True) +def card_badge(context, card, quantity=None, expanded=False): + """ + Renders a card badge. + """ + url = reverse_lazy('cards:card_detail', args=[card.pk]) + tag_context = { 'quantity': quantity, 'style': card.style, 'name': card.name, 'rarity': card.rarity_icon, 'cardset': card.cardset, 'expanded': expanded, + 'cache_key': f'card_badge_{card.pk}_{quantity}_{expanded}', 'url': url, } + context.update(tag_context) + return context @register.filter def card_badge_inline(card, quantity=None): """ - Renders an inline card badge. + Renders an inline card badge by directly rendering the template. """ - url = reverse('cards:card_detail', args=[card.pk]) - html = render_to_string("templatetags/card_badge.html", { + url = reverse_lazy('cards:card_detail', args=[card.pk]) + tag_context = { 'quantity': quantity, 'style': card.style, 'name': card.name, 'rarity': card.rarity_icon, 'cardset': card.cardset, 'expanded': True, + 'cache_key': f'card_badge_{card.pk}_{quantity}_{True}', + 'CACHE_TIMEOUT': settings.CACHE_TIMEOUT, 'url': url, - }) - return mark_safe(html) - -@register.filter -def addstr(arg1, arg2): - """concatenate arg1 & arg2""" - return str(arg1) + str(arg2) \ No newline at end of file + } + html = render_to_string("templatetags/card_badge.html", tag_context) + return mark_safe(html) \ No newline at end of file diff --git a/cards/templatetags/card_multiselect.py b/cards/templatetags/card_multiselect.py index ba0fdb5..9952cb0 100644 --- a/cards/templatetags/card_multiselect.py +++ b/cards/templatetags/card_multiselect.py @@ -1,54 +1,72 @@ +import uuid from django import template from cards.models import Card - +from django.db.models.query import QuerySet +import json +import hashlib +import logging register = template.Library() -@register.inclusion_tag('templatetags/card_multiselect.html') -def card_multiselect(field_name, label, placeholder, cards=None, selected_values=None, cache_timeout=86400): +@register.filter +def get_item(dictionary, key): + """Allows accessing dictionary items using a variable key in templates.""" + return dictionary.get(key) + +@register.simple_tag +def fetch_all_cards(): + """Simple tag to fetch all Card objects.""" + return Card.objects.order_by('pk').all() + +@register.inclusion_tag('templatetags/card_multiselect.html', takes_context=True) +def card_multiselect(context, field_name, label, placeholder, cards=None, selected_values=None): """ - Renders a multiselect field for choosing cards while supporting quantity data. - - Updated to allow `card_filter` to be either a dictionary (of lookup parameters) or a QuerySet. - This is useful when you want to limit available cards based on your new trades models (e.g. showing only - cards that appear in active trade offers). - - Parameters: - - field_name: The name attribute for the select tag. - - label: Label text to show above the selector. - - placeholder: Placeholder text to show in the select. - - selected_values: (Optional) A list of selected values; if a value includes a quantity it should be in the format "card_id:quantity". - - cache_timeout: (Optional) Cache timeout (in seconds) for the options block. - - cache_key: (Optional) Cache key. + Prepares context for rendering a card multiselect input. + Database querying and rendering are handled within the template's cache block. """ if selected_values is None: selected_values = [] - # Create a mapping {card_id: quantity} + selected_cards = {} for val in selected_values: parts = str(val).split(':') - card_id = parts[0] - quantity = parts[1] if len(parts) > 1 else 1 - selected_cards[card_id] = quantity + if len(parts) >= 1 and parts[0]: + card_id = parts[0] + quantity = parts[1] if len(parts) > 1 else 1 + selected_cards[str(card_id)] = quantity - if cards is None: - cards = Card.objects.all() + effective_field_name = field_name if field_name is not None else 'card_multiselect' + effective_label = label if label is not None else 'Card' + effective_placeholder = placeholder if placeholder is not None else 'Select Cards' - # Loop through available cards and attach pre‑selected quantity - for card in cards: - pk_str = str(card.pk) - if pk_str in selected_cards: - card.selected_quantity = selected_cards[pk_str] - card.selected = True - else: - card.selected_quantity = 1 - card.selected = False + selected_cards_key_part = json.dumps(selected_cards, sort_keys=True) - return { - 'field_name': field_name, - 'field_id': field_name, # using the name as id for simplicity - 'label': label, - 'cards': cards, - 'placeholder': placeholder, - 'selected_values': list(selected_cards.keys()), - 'cache_timeout': cache_timeout - } \ No newline at end of file + has_passed_cards = isinstance(cards, QuerySet) + + if has_passed_cards: + try: + query_string = str(cards.query) + passed_cards_identifier = hashlib.sha256(query_string.encode('utf-8')).hexdigest() + except Exception as e: + logging.warning(f"Could not generate query hash for card_multiselect. Error: {e}") + passed_cards_identifier = 'specific_qs_fallback_' + str(uuid.uuid4()) + else: + passed_cards_identifier = 'all_cards' + + # Define the variables specific to this tag + tag_specific_context = { + 'field_name': effective_field_name, + 'field_id': effective_field_name, + 'label': effective_label, + 'placeholder': effective_placeholder, + 'passed_cards': cards if has_passed_cards else None, + 'has_passed_cards': has_passed_cards, + 'selected_cards': selected_cards, + 'selected_cards_key_part': selected_cards_key_part, + 'passed_cards_identifier': passed_cards_identifier, + } + + # Update the original context with the tag-specific variables + # This preserves CACHE_TIMEOUT and other parent context variables + context.update(tag_specific_context) + + return context # Return the MODIFIED original context \ No newline at end of file diff --git a/theme/templatetags/_card_multiselect_options.html b/theme/templatetags/_card_multiselect_options.html new file mode 100644 index 0000000..a1c0b3b --- /dev/null +++ b/theme/templatetags/_card_multiselect_options.html @@ -0,0 +1,30 @@ +{% load card_badge card_multiselect %} + +{% for card in cards_to_render %} + {% with card_id_str=card.pk|stringformat:"s" %} {# Ensure card PK is string for lookup #} + {% if card_id_str in selected_cards %} + + {% else %} + + {% endif %} + {% endwith %} +{% endfor %} \ No newline at end of file diff --git a/theme/templatetags/card_badge.html b/theme/templatetags/card_badge.html index b425a32..78fba7f 100644 --- a/theme/templatetags/card_badge.html +++ b/theme/templatetags/card_badge.html @@ -1,26 +1,29 @@ - -
- {% if not expanded %} -
-
{{ name }}
-
{{ cardset }}
- {% if quantity != None %} -
-
{{ quantity }}
-
- {% endif %} -
- {% else %} -
-
{{ name }}
- {% if quantity != None %} -
-
{{ quantity }}
-
- {% endif %} -
{{ rarity }}
-
{{ cardset }}
-
- {% endif %} -
-
\ No newline at end of file +{% load cache %} +{% cache CACHE_TIMEOUT card_badge cache_key %} + +
+ {% if not expanded %} +
+
{{ name }}
+
{{ cardset }}
+ {% if quantity != None %} +
+
{{ quantity }}
+
+ {% endif %} +
+ {% else %} +
+
{{ name }}
+ {% if quantity != None %} +
+
{{ quantity }}
+
+ {% endif %} +
{{ rarity }}
+
{{ cardset }}
+
+ {% endif %} +
+
+{% endcache %} \ No newline at end of file diff --git a/theme/templatetags/card_multiselect.html b/theme/templatetags/card_multiselect.html index 2dfd671..f69fd1d 100644 --- a/theme/templatetags/card_multiselect.html +++ b/theme/templatetags/card_multiselect.html @@ -1,22 +1,16 @@ {% load cache card_badge %} +{% load cache card_multiselect %} + \ No newline at end of file diff --git a/theme/templatetags/trade_acceptance.html b/theme/templatetags/trade_acceptance.html index 75042bc..f5c1fcb 100644 --- a/theme/templatetags/trade_acceptance.html +++ b/theme/templatetags/trade_acceptance.html @@ -1,58 +1,60 @@ {% load gravatar card_badge %} -
- - -
-
- -
-
-
- {{ acceptance.trade_offer.initiated_by.user.email|gravatar:40 }} +{% cache CACHE_TIMEOUT trade_acceptance cache_key %} + - - -
-
-
- {% card_badge acceptance.requested_card %} -
-
- {% card_badge acceptance.offered_card %} -
-
-
- - +{% endcache %} \ No newline at end of file diff --git a/theme/templatetags/trade_offer.html b/theme/templatetags/trade_offer.html index 1d11d29..adb011b 100644 --- a/theme/templatetags/trade_offer.html +++ b/theme/templatetags/trade_offer.html @@ -1,6 +1,6 @@ {% load gravatar card_badge cache %} -{% cache 60 trade_offer offer_pk %} +{% cache CACHE_TIMEOUT trade_offer cache_key %}
card.qty_accepted ] - acceptances = list(offer.acceptances.all()) - - # Determine if the offer should show its back side (acceptances view) by default. - # If either side has no available cards, then flip the offer. if not have_cards_available or not want_cards_available: flipped = True else: flipped = False - return { + tag_context = { 'offer_pk': offer.pk, - 'flipped': flipped, # new flag to control the default face + 'flipped': flipped, 'offer_hash': offer.hash, 'rarity_icon': offer.rarity_icon, 'initiated_by_email': offer.initiated_by.user.email, @@ -43,19 +41,25 @@ def render_trade_offer(context, offer): 'want_cards_available': want_cards_available, 'num_cards_available': len(have_cards_available) + len(want_cards_available), 'on_detail_page': context.get("request").path.endswith("trades/"+str(offer.pk)+"/"), + 'cache_key': f'trade_offer_{offer.pk}_{offer.updated_at.timestamp()}_{flipped}', } + context.update(tag_context) + return context + @register.inclusion_tag('templatetags/trade_acceptance.html', takes_context=True) def render_trade_acceptance(context, acceptance): """ Renders a simple trade acceptance view with a single row and simplified header/footer. """ - - return { + tag_context = { "acceptance": acceptance, - "request": context.get("request"), + 'cache_key': f'trade_acceptance_{acceptance.pk}_{acceptance.updated_at.timestamp()}', } + context.update(tag_context) + return context + @register.filter def get_action_label(acceptance, state_value): """