Enable caching via DB and update cache timeouts

This commit is contained in:
badblocks 2025-03-15 21:51:23 -07:00
parent 9ce5d525b3
commit 27c7238a82
11 changed files with 281 additions and 283 deletions

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-15 22:05 # Generated by Django 5.1.2 on 2025-03-16 04:58
import django.contrib.auth.models import django.contrib.auth.models
import django.contrib.auth.validators import django.contrib.auth.validators

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-15 22:05 # Generated by Django 5.1.2 on 2025-03-16 04:58
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View file

@ -4,7 +4,7 @@ from cards.models import Card
register = template.Library() register = template.Library()
@register.inclusion_tag('templatetags/card_multiselect.html') @register.inclusion_tag('templatetags/card_multiselect.html')
def card_multiselect(field_name, label, placeholder, cards=None, selected_values=None, cache_timeout=86400, cache_key="cards_multiselect"): def card_multiselect(field_name, label, placeholder, cards=None, selected_values=None, cache_timeout=86400):
""" """
Renders a multiselect field for choosing cards while supporting quantity data. Renders a multiselect field for choosing cards while supporting quantity data.
@ -50,6 +50,5 @@ def card_multiselect(field_name, label, placeholder, cards=None, selected_values
'cards': cards, 'cards': cards,
'placeholder': placeholder, 'placeholder': placeholder,
'selected_values': list(selected_cards.keys()), 'selected_values': list(selected_cards.keys()),
'cache_timeout': cache_timeout, 'cache_timeout': cache_timeout
'cache_key': cache_key,
} }

View file

@ -286,7 +286,7 @@ if DEBUG:
else: else:
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache", "BACKEND": "django.core.cache.backends.db.DatabaseCache",
"LOCATION": "unique-snowflake", "LOCATION": "site_cache",
} }
} }

View file

@ -10,7 +10,6 @@ from django.views.decorators.cache import cache_page
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
@method_decorator(cache_page(60), name='get')
class HomePageView(TemplateView): class HomePageView(TemplateView):
template_name = "home/home.html" template_name = "home/home.html"

View file

@ -22,5 +22,8 @@ uv run python manage.py migrate
echo "Loading seed data..." echo "Loading seed data..."
uv run python manage.py loaddata seed/0* uv run python manage.py loaddata seed/0*
echo "Creating cache table..."
uv run python manage.py createcachetable
echo "Seeding default friend codes..." echo "Seeding default friend codes..."
uv run python manage.py seed_default_friend_codes uv run python manage.py seed_default_friend_codes

View file

@ -85,9 +85,9 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Featured Offers --> <!-- Featured Offers -->
<div> <div>
{% cache 86400 featured_offers %} {% cache 60 featured_offers %}
<div class="p-4"> <div class="p-4 text-center ">
<h5 class="text-xl text-center font-semibold whitespace-nowrap truncate mb-0">Featured Offers</h5> <h5 class="text-xl font-semibold whitespace-nowrap truncate mb-0">Featured Offers</h5>
</div> </div>
<div class="p-4"> <div class="p-4">
<!-- Tab contents --> <!-- Tab contents -->
@ -139,7 +139,7 @@
<!-- Recent Offers --> <!-- Recent Offers -->
<div> <div>
{% cache 60 recent_offers %} {% cache 60 recent_offers %}
<div class="text-center text-base-content p-4"> <div class="text-center p-4">
<h5 class="text-xl font-semibold whitespace-nowrap truncate mb-0">Recent Offers</h5> <h5 class="text-xl font-semibold whitespace-nowrap truncate mb-0">Recent Offers</h5>
</div> </div>
<div class="p-4"> <div class="p-4">

View file

@ -3,7 +3,7 @@
<span class="label-text">{{ label }}</span> <span class="label-text">{{ label }}</span>
</label> </label>
<select name="{{ field_name }}" id="{{ field_id }}" class="select select-bordered w-full card-multiselect" data-placeholder="{{ placeholder }}" multiple> <select name="{{ field_name }}" id="{{ field_id }}" class="select select-bordered w-full card-multiselect" data-placeholder="{{ placeholder }}" multiple>
{% cache cache_timeout cache_key selected_values|join:"," %} {% cache cache_timeout card_multiselect selected_values|join:"," %}
<option value="" disabled>{{ placeholder }}</option> <option value="" disabled>{{ placeholder }}</option>
{% for card in cards %} {% for card in cards %}
<option <option

View file

@ -1,51 +1,53 @@
{% load gravatar card_badge %} {% load gravatar card_badge cache %}
<div class="card card-border bg-base-100 shadow-lg w-96 md:w-80 lg:w-96"> {% cache 60 trade_acceptance acceptance.pk %}
<!-- Header --> <div class="card card-border bg-base-100 shadow-lg w-96 md:w-80 lg:w-96">
<div class="py-4 mx-2 sm:mx-4"> <!-- Header -->
<div class="flex justify-between items-center"> <div class="py-4 mx-2 sm:mx-4">
<!-- Left: Initiator's avatar (moved from center) and "Has" --> <div class="flex justify-between items-center">
<div class="flex items-center"> <!-- Left: Initiator's avatar (moved from center) and "Has" -->
<div class="avatar mr-2"> <div class="flex items-center">
<div class="w-10 rounded-full"> <div class="avatar mr-2">
{{ acceptance.trade_offer.initiated_by.user.email|gravatar:40 }} <div class="w-10 rounded-full">
{{ acceptance.trade_offer.initiated_by.user.email|gravatar:40 }}
</div>
</div> </div>
<span class="text-sm font-semibold">Has</span>
</div> </div>
<span class="text-sm font-semibold">Has</span> <!-- Right: "Wants" with the acceptor's avatar -->
</div> <div class="flex items-center">
<!-- Right: "Wants" with the acceptor's avatar --> <span class="text-sm font-semibold mr-2">Wants</span>
<div class="flex items-center"> <div class="avatar">
<span class="text-sm font-semibold mr-2">Wants</span> <div class="w-10 rounded-full">
<div class="avatar"> {{ acceptance.accepted_by.user.email|gravatar:40 }}
<div class="w-10 rounded-full"> </div>
{{ acceptance.accepted_by.user.email|gravatar:40 }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Main Card Row: Single row with the acceptance's cards -->
<!-- Main Card Row: Single row with the acceptance's cards --> <a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline block">
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline block"> <div class="px-2 pb-0">
<div class="px-2 pb-0"> <div class="grid grid-cols-2 items-center border-t border-gray-300">
<div class="grid grid-cols-2 items-center border-t border-gray-300"> <div class="flex flex-col items-center">
<div class="flex flex-col items-center"> {% card_badge acceptance.requested_card %}
{% card_badge acceptance.requested_card %} </div>
</div> <div class="flex flex-col items-center">
<div class="flex flex-col items-center"> {% card_badge acceptance.offered_card %}
{% card_badge acceptance.offered_card %} </div>
</div> </div>
</div> </div>
</a>
<!-- Footer: Only info button with acceptance hash -->
<div class="flex justify-end px-2 pb-2">
<div class="text-gray-500 text-sm tooltip tooltip-left" data-tip="Acceptance ID: {{ acceptance.hash }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M11.25 11.25l.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0zm-9-3.75h.008v.008H12V8.25z" />
</svg>
</div>
</div> </div>
</a> </div>
{% endcache %}
<!-- Footer: Only info button with acceptance hash -->
<div class="flex justify-end px-2 pb-2">
<div class="text-gray-500 text-sm tooltip tooltip-left" data-tip="Acceptance ID: {{ acceptance.hash }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M11.25 11.25l.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0zm-9-3.75h.008v.008H12V8.25z" />
</svg>
</div>
</div>
</div>

View file

@ -1,46 +1,135 @@
{% load gravatar card_badge %} {% load gravatar card_badge cache %}
<!-- {% cache 60 trade_offer offer.pk %}
Define the Alpine component only once. <script>
--> if (!window.tradeOfferCard) {
<script> window.tradeOfferCard = function() {
if (!window.tradeOfferCard) { return {
window.tradeOfferCard = function() { flipped: false,
return { badgeExpanded: false,
flipped: false, acceptanceExpanded: false,
badgeExpanded: false, // Helper method to set the badgeExpanded state
acceptanceExpanded: false, setBadge(expanded) {
// Helper method to set the badgeExpanded state this.badgeExpanded = expanded;
setBadge(expanded) { },
this.badgeExpanded = expanded; };
}, }
};
} }
} </script>
</script>
<!-- <div x-data="tradeOfferCard()" class="transition-all duration-500 trade-offer-card"
The outer div now only establishes Alpine's data context. @toggle-all.window="setBadge($event.detail.expanded)">
The dynamic height adjustment (x-init & x-effect with x-ref) has been removed.
-->
<div x-data="tradeOfferCard()" class="transition-all duration-500 trade-offer-card"
@toggle-all.window="setBadge($event.detail.expanded)">
<!-- Flip container providing perspective --> <!-- Flip container providing perspective -->
<div class="flip-container" style="perspective: 1000px;"> <div class="flip-container" style="perspective: 1000px;">
<!-- <!--
The rotating element (.flip-inner) now uses CSS Grid to stack its children in a single cell. The rotating element (.flip-inner) now uses CSS Grid to stack its children in a single cell.
Persistent border, shadow, and rounding are applied here and the card rotates entirely. Persistent border, shadow, and rounding are applied here and the card rotates entirely.
--> -->
<div class="flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg w-96 md:w-80 lg:w-96 transform transition-transform duration-700 ease-in-out" <div class="flip-inner grid grid-cols-1 grid-rows-1 card bg-base-100 card-border shadow-lg w-96 md:w-80 lg:w-96 transform transition-transform duration-700 ease-in-out"
:class="{'rotate-y-180': flipped}"> :class="{'rotate-y-180': flipped}">
<!-- Front Face: Trade Offer --> <!-- Front Face: Trade Offer -->
<!-- Using grid placement classes (col-start-1 row-start-1) ensures both faces overlap --> <!-- Using grid placement classes (col-start-1 row-start-1) ensures both faces overlap -->
<div class="flip-face front col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between"> <div class="flip-face front col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between">
<!-- Header --> <!-- Header -->
<div class="self-start">
<a href="{% url 'trade_offer_detail' pk=offer.pk %}" class="no-underline block">
<div class="py-4 mx-2 sm:mx-4">
<div class="grid grid-cols-3 items-center">
<div class="flex justify-center items-center">
<span class="text-sm font-semibold">Has</span>
</div>
<div class="flex justify-center items-center">
<div class="avatar">
<div class="w-10 rounded-full">
{{ offer.initiated_by.user.email|gravatar:40 }}
</div>
</div>
</div>
<div class="flex justify-center items-center">
<span class="text-sm font-semibold">Wants</span>
</div>
</div>
</div>
</a>
</div>
<!-- Main Trade Offer Row -->
<div class="self-start">
<a href="{% url 'trade_offer_detail' pk=offer.pk %}" class="no-underline block">
<div class="px-2 pb-0">
<div class="grid grid-cols-2 gap-2 items-center">
<div class="flex flex-col items-center">
{% if have_cards_available %}
{% with first_have=have_cards_available.0 %}
{% card_badge first_have.card first_have.quantity %}
{% endwith %}
{% endif %}
</div>
<div class="flex flex-col items-center">
{% if want_cards_available %}
{% with first_want=want_cards_available.0 %}
{% card_badge first_want.card first_want.quantity %}
{% endwith %}
{% endif %}
</div>
</div>
</div>
<!-- Extra Card Badges (Collapsible) -->
<div x-show="badgeExpanded" x-collapse.duration.500ms class="px-2">
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-col items-center">
{% for th in have_cards_available|slice:"1:" %}
{% card_badge th.card th.quantity %}
{% endfor %}
</div>
<div class="flex flex-col items-center">
{% for th in want_cards_available|slice:"1:" %}
{% card_badge th.card th.quantity %}
{% endfor %}
</div>
</div>
</div>
</a>
<div class="flex justify-center my-1 h-5">
{% if have_cards_available|length > 1 or want_cards_available|length > 1 %}
<svg @click="badgeExpanded = !badgeExpanded"
x-bind:class="{ 'rotate-180': badgeExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" />
</svg>
{% endif %}
</div>
</div>
<div class="self-end">
<div class="flex justify-between px-2 pb-2">
<div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer.hash }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg>
</div>
<!-- Front-to-back flip button -->
<div class="cursor-pointer text-gray-500"
@click="if(badgeExpanded){ badgeExpanded = false; setTimeout(() => { flipped = true; }, 500); } else { flipped = true; }">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061A1.125 1.125 0 0 1 3 16.811V8.69ZM12.75 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061a1.125 1.125 0 0 1-1.683-.977V8.69Z" />
</svg>
</div>
</div>
</div>
</div>
<!-- Back Face: Acceptances View -->
<!-- Placed in the same grid cell as the front face -->
<div class="flip-face back col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between" style="transform: rotateY(180deg);">
<div class="self-start"> <div class="self-start">
<a href="{% url 'trade_offer_detail' pk=offer.pk %}" class="no-underline block"> <a href="{% url 'trade_offer_detail' pk=offer.pk %}" class="no-underline">
<div class="py-4 mx-2 sm:mx-4"> <div class="py-4 mx-2 sm:mx-4">
<div class="grid grid-cols-3 items-center"> <div class="grid grid-cols-3 items-center">
<div class="flex justify-center items-center"> <div class="flex justify-center items-center">
@ -60,215 +149,121 @@ if (!window.tradeOfferCard) {
</div> </div>
</a> </a>
</div> </div>
<!-- Main Trade Offer Row -->
<div class="self-start"> <div class="self-start">
<a href="{% url 'trade_offer_detail' pk=offer.pk %}" class="no-underline block"> <div class="px-2 pb-0">
<div class="px-2 pb-0"> <div class="overflow-hidden">
<div class="grid grid-cols-2 gap-2 items-center"> {% if offer.acceptances.first %}
<div class="flex flex-col items-center"> <div class="space-y-3">
{% if have_cards_available %} {% with acceptance=offer.acceptances.first %}
{% with first_have=have_cards_available.0 %} <a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline"
{% card_badge first_have.card first_have.quantity %} data-tooltip-html='<div class="flex items-center space-x-2">
{% endwith %} <div class="avatar">
{% endif %} <div class="w-10 rounded-full">
{{ acceptance.accepted_by.user.email|gravatar:"40" }}
</div>
</div>
<div class="flex flex-col">
<span class="text-sm">Accepted by: {{ acceptance.accepted_by.user.username }}</span>
<span class="text-sm">State: {{ acceptance.state }}</span>
<span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span>
</div>
</div>'>
<div class="grid grid-cols-2 gap-4 items-center">
<div>
{% card_badge acceptance.requested_card %}
</div>
<div>
{% card_badge acceptance.offered_card %}
</div>
</div>
</a>
{% endwith %}
</div> </div>
<div class="flex flex-col items-center"> {% endif %}
{% if want_cards_available %}
{% with first_want=want_cards_available.0 %}
{% card_badge first_want.card first_want.quantity %}
{% endwith %}
{% endif %}
</div>
</div>
</div> </div>
<div x-show="acceptanceExpanded" x-collapse.duration.500ms class="space-y-3">
<!-- Extra Card Badges (Collapsible) --> {% for acceptance in offer.acceptances.all|slice:"1:" %}
<div x-show="badgeExpanded" x-collapse.duration.500ms class="px-2"> <a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline"
<div class="grid grid-cols-2 gap-2"> data-tooltip-html='<div class="flex items-center space-x-2">
<div class="flex flex-col items-center"> <div class="avatar">
{% for th in have_cards_available|slice:"1:" %} <div class="w-10 rounded-full">
{% card_badge th.card th.quantity %} {{ acceptance.accepted_by.user.email|gravatar:"40" }}
{% endfor %} </div>
</div>
<div class="flex flex-col">
<span class="text-sm">Accepted by: {{ acceptance.accepted_by.user.username }}</span>
<span class="text-sm">State: {{ acceptance.state }}</span>
<span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span>
</div>
</div>'>
<div class="grid grid-cols-2 gap-4 items-center">
<div>
{% card_badge acceptance.requested_card %}
</div>
<div>
{% card_badge acceptance.offered_card %}
</div>
</div> </div>
<div class="flex flex-col items-center"> </a>
{% for th in want_cards_available|slice:"1:" %} {% endfor %}
{% card_badge th.card th.quantity %}
{% endfor %}
</div>
</div>
</div> </div>
</a> </div>
<div class="flex justify-center my-1 h-5"> <div class="flex justify-center my-1 h-5">
{% if have_cards_available|length > 1 or want_cards_available|length > 1 %} {% if offer.acceptances.all|length > 1 %}
<svg @click="badgeExpanded = !badgeExpanded" <svg @click="acceptanceExpanded = !acceptanceExpanded"
x-bind:class="{ 'rotate-180': badgeExpanded }" x-bind:class="{ 'rotate-180': acceptanceExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer" class="transition-transform duration-500 h-5 w-5 cursor-pointer"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" /> d="M19 9l-7 7-7-7" />
</svg> </svg>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="self-end"> <div class="flex justify-between px-2 pb-2 self-end">
<div class="flex justify-between px-2 pb-2"> <!-- Back-to-front flip button -->
<div class="text-gray-500 text-sm tooltip tooltip-right" data-tip="ID: {{ offer.hash }}"> <div class="text-gray-500 cursor-pointer"
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" @click="if(acceptanceExpanded){ acceptanceExpanded = false; setTimeout(() => { flipped = false; }, 500); } else { flipped = false; }">
stroke="currentColor" class="size-5"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" <path stroke-linecap="round" stroke-linejoin="round" d="M21 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061A1.125 1.125 0 0 1 21 8.689v8.122ZM11.25 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061a1.125 1.125 0 0 1 1.683.977v8.122Z" />
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg> </svg>
</div>
<!-- Front-to-back flip button -->
<div class="cursor-pointer text-gray-500"
@click="if(badgeExpanded){ badgeExpanded = false; setTimeout(() => { flipped = true; }, 500); } else { flipped = true; }">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061A1.125 1.125 0 0 1 3 16.811V8.69ZM12.75 8.689c0-.864.933-1.406 1.683-.977l7.108 4.061a1.125 1.125 0 0 1 0 1.954l-7.108 4.061a1.125 1.125 0 0 1-1.683-.977V8.69Z" />
</svg>
</div>
</div> </div>
</div> <div class="px-1 text-center">
</div> <span class="text-sm font-semibold">
Acceptances ({{ offer.acceptances.all|length }})
<!-- Back Face: Acceptances View --> </span>
<!-- Placed in the same grid cell as the front face -->
<div class="flip-face back col-start-1 row-start-1 grid grid-cols-1 auto-rows-min gap-2 content-between" style="transform: rotateY(180deg);">
<div class="self-start">
<a href="{% url 'trade_offer_detail' pk=offer.pk %}" class="no-underline">
<div class="py-4 mx-2 sm:mx-4">
<div class="grid grid-cols-3 items-center">
<div class="flex justify-center items-center">
<span class="text-sm font-semibold">Has</span>
</div>
<div class="flex justify-center items-center">
<div class="avatar">
<div class="w-10 rounded-full">
{{ offer.initiated_by.user.email|gravatar:40 }}
</div>
</div>
</div>
<div class="flex justify-center items-center">
<span class="text-sm font-semibold">Wants</span>
</div>
</div>
</div> </div>
</a> <div class="text-gray-500 text-sm tooltip tooltip-left" data-tip="ID: {{ offer.hash }}">
</div> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
<div class="self-start"> stroke="currentColor" class="size-5">
<div class="px-2 pb-0"> <path stroke-linecap="round" stroke-linejoin="round"
<div class="overflow-hidden"> d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
{% if offer.acceptances.first %}
<div class="space-y-3">
{% with acceptance=offer.acceptances.first %}
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline"
data-tooltip-html='<div class="flex items-center space-x-2">
<div class="avatar">
<div class="w-10 rounded-full">
{{ acceptance.accepted_by.user.email|gravatar:"40" }}
</div>
</div>
<div class="flex flex-col">
<span class="text-sm">Accepted by: {{ acceptance.accepted_by.user.username }}</span>
<span class="text-sm">State: {{ acceptance.state }}</span>
<span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span>
</div>
</div>'>
<div class="grid grid-cols-2 gap-4 items-center">
<div>
{% card_badge acceptance.requested_card %}
</div>
<div>
{% card_badge acceptance.offered_card %}
</div>
</div>
</a>
{% endwith %}
</div>
{% endif %}
</div>
<div x-show="acceptanceExpanded" x-collapse.duration.500ms class="space-y-3">
{% for acceptance in offer.acceptances.all|slice:"1:" %}
<a href="{% url 'trade_acceptance_update' pk=acceptance.pk %}" class="no-underline"
data-tooltip-html='<div class="flex items-center space-x-2">
<div class="avatar">
<div class="w-10 rounded-full">
{{ acceptance.accepted_by.user.email|gravatar:"40" }}
</div>
</div>
<div class="flex flex-col">
<span class="text-sm">Accepted by: {{ acceptance.accepted_by.user.username }}</span>
<span class="text-sm">State: {{ acceptance.state }}</span>
<span class="text-sm">Acceptance ID: {{ acceptance.hash }}</span>
</div>
</div>'>
<div class="grid grid-cols-2 gap-4 items-center">
<div>
{% card_badge acceptance.requested_card %}
</div>
<div>
{% card_badge acceptance.offered_card %}
</div>
</div>
</a>
{% endfor %}
</div>
</div>
<div class="flex justify-center my-1 h-5">
{% if offer.acceptances.all|length > 1 %}
<svg @click="acceptanceExpanded = !acceptanceExpanded"
x-bind:class="{ 'rotate-180': acceptanceExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" />
</svg> </svg>
{% endif %}
</div> </div>
</div>
<div class="flex justify-between px-2 pb-2 self-end">
<!-- Back-to-front flip button -->
<div class="text-gray-500 cursor-pointer"
@click="if(acceptanceExpanded){ acceptanceExpanded = false; setTimeout(() => { flipped = false; }, 500); } else { flipped = false; }">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061A1.125 1.125 0 0 1 21 8.689v8.122ZM11.25 16.811c0 .864-.933 1.406-1.683.977l-7.108-4.061a1.125 1.125 0 0 1 0-1.954l7.108-4.061a1.125 1.125 0 0 1 1.683.977v8.122Z" />
</svg>
</div>
<div class="px-1 text-center">
<span class="text-sm font-semibold">
Acceptances ({{ offer.acceptances.all|length }})
</span>
</div>
<div class="text-gray-500 text-sm tooltip tooltip-left" data-tip="ID: {{ offer.hash }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
</svg>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<style> <style>
/* Ensure proper 3D transformations on the rotating element */ /* Ensure proper 3D transformations on the rotating element */
.flip-inner { .flip-inner {
transform-style: preserve-3d; transform-style: preserve-3d;
} }
/* Hide the back face of each card side */ /* Hide the back face of each card side */
.flip-face { .flip-face {
backface-visibility: hidden; backface-visibility: hidden;
-webkit-backface-visibility: hidden; -webkit-backface-visibility: hidden;
} }
/* The front face is unrotated by default */ /* The front face is unrotated by default */
.flip-face.front { .flip-face.front {
transform: rotateY(0); transform: rotateY(0);
} }
/* The .rotate-y-180 class rotates the entire element by 180deg */ /* The .rotate-y-180 class rotates the entire element by 180deg */
.rotate-y-180 { .rotate-y-180 {
transform: rotateY(180deg); transform: rotateY(180deg);
} }
</style> </style>
{% endcache %}

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-15 22:05 # Generated by Django 5.1.2 on 2025-03-16 04:58
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models