fix card_multiselect filtering and quantity controls
This commit is contained in:
parent
6e4c6040bd
commit
b97ddde71c
52 changed files with 1689 additions and 2268 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
|
.env.production
|
||||||
# OSX #
|
# OSX #
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ RUN set -ex && \
|
||||||
|
|
||||||
# Copy local project
|
# Copy local project
|
||||||
COPY . /code/
|
COPY . /code/
|
||||||
|
COPY .env.production /code/.env
|
||||||
ENV HOME=/code
|
ENV HOME=/code
|
||||||
|
|
||||||
# Install NPM & node.js
|
# Install NPM & node.js
|
||||||
|
|
@ -32,5 +33,4 @@ EXPOSE 8000
|
||||||
RUN python manage.py collectstatic --noinput
|
RUN python manage.py collectstatic --noinput
|
||||||
|
|
||||||
# Use gunicorn on port 8000
|
# Use gunicorn on port 8000
|
||||||
#CMD ["/bin/bash", "-c", "python manage.py collectstatic --noinput; gunicorn --bind :8000 --workers 2 django_project.wsgi"]
|
|
||||||
CMD ["gunicorn", "--bind", ":8000", "--workers", "2", "django_project.wsgi"]
|
CMD ["gunicorn", "--bind", ":8000", "--workers", "2", "django_project.wsgi"]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.1.2 on 2025-03-09 05:08
|
# Generated by Django 5.1.2 on 2025-03-13 01:46
|
||||||
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.contrib.auth.validators
|
import django.contrib.auth.validators
|
||||||
|
|
@ -13,7 +13,7 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('auth', '0012_alter_user_first_name_max_length'),
|
('auth', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.1.2 on 2025-03-09 05:08
|
# Generated by Django 5.1.2 on 2025-03-13 01:46
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
@ -89,4 +89,15 @@ class Migration(migrations.Migration):
|
||||||
name='rarity',
|
name='rarity',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cards', to='cards.rarity'),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cards', to='cards.rarity'),
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RarityNameTranslation',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=64)),
|
||||||
|
('language', models.CharField(max_length=64)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('rarity', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='name_translations', to='cards.rarity')),
|
||||||
|
],
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,17 @@ class CardNameTranslation(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
class RarityNameTranslation(models.Model):
|
||||||
|
id = models.AutoField(primary_key=True)
|
||||||
|
name = models.CharField(max_length=64)
|
||||||
|
rarity = models.ForeignKey("Rarity", on_delete=models.PROTECT, related_name='name_translations')
|
||||||
|
language = models.CharField(max_length=64)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
class CardSet(models.Model):
|
class CardSet(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
name = models.CharField(max_length=64)
|
name = models.CharField(max_length=64)
|
||||||
|
|
|
||||||
|
|
@ -48,20 +48,13 @@ def card_multiselect(field_name, label, placeholder, card_filter=None, selected_
|
||||||
.prefetch_related("decks")
|
.prefetch_related("decks")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Loop through available cards and set styling, plus attach pre‑selected quantity
|
# Loop through available cards and attach pre‑selected quantity
|
||||||
for card in available_cards:
|
for card in available_cards:
|
||||||
# decks = list(card.decks.all())
|
|
||||||
# deck_count = len(decks)
|
|
||||||
# if deck_count == 1:
|
|
||||||
# card.style = f"background-color: {decks[0].hex_color}; color: white;"
|
|
||||||
# elif deck_count == 2:
|
|
||||||
# card.style = f"background: linear-gradient(to right, {decks[0].hex_color}, {decks[1].hex_color}); color: white;"
|
|
||||||
# elif deck_count >= 3:
|
|
||||||
# card.style = f"background: linear-gradient(to right, {decks[0].hex_color}, {decks[1].hex_color}, {decks[2].hex_color}); color: white;"
|
|
||||||
|
|
||||||
pk_str = str(card.pk)
|
pk_str = str(card.pk)
|
||||||
if pk_str in selected_cards:
|
if pk_str in selected_cards:
|
||||||
card.selected_quantity = selected_cards[pk_str]
|
card.selected_quantity = selected_cards[pk_str]
|
||||||
|
else:
|
||||||
|
card.selected_quantity = 1
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'field_name': field_name,
|
'field_name': field_name,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import environ
|
||||||
|
import os
|
||||||
|
|
||||||
|
env = environ.Env(
|
||||||
|
DEBUG=(bool, False)
|
||||||
|
)
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# Take environment variables from .env file
|
||||||
|
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
|
||||||
|
|
||||||
|
|
@ -15,7 +24,7 @@ RESEND_API_KEY = "re_BBXJWctP_8gb4iNpfaHuau7Na95mc3feu"
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = env('DEBUG')
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||||
ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "pkmntrade-club.fly.dev", "pkmntrade.club"]
|
ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1", "pkmntrade-club.fly.dev", "pkmntrade.club"]
|
||||||
|
|
@ -97,42 +106,24 @@ TEMPLATES = [
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
|
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"local": {
|
'default': env.db(),
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
|
||||||
"NAME": "postgres",
|
|
||||||
"USER": "postgres",
|
|
||||||
"PASSWORD": "",
|
|
||||||
"HOST": "localhost", # set in docker-compose.yml
|
|
||||||
"PORT": 5432, # default postgres port
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
|
||||||
"NAME": "dev",
|
|
||||||
"USER": "pocket_trade_owner",
|
|
||||||
"PASSWORD": "npg_f1lTpOX7Rnvb",
|
|
||||||
"HOST": "ep-cool-cake-a6zvgu85-pooler.us-west-2.aws.neon.tech", # set in docker-compose.yml
|
|
||||||
"PORT": 5432, # default postgres port
|
|
||||||
"OPTIONS": {
|
|
||||||
"sslmode": "require"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
# {
|
{
|
||||||
# "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
# },
|
},
|
||||||
# {
|
{
|
||||||
# "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
# },
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -228,8 +219,7 @@ ACCOUNT_SESSION_REMEMBER = True
|
||||||
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = True
|
ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = True
|
||||||
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
|
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
|
||||||
ACCOUNT_EMAIL_REQUIRED = True
|
ACCOUNT_EMAIL_REQUIRED = True
|
||||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
ACCOUNT_EMAIL_VERIFICATION = env('ACCOUNT_EMAIL_VERIFICATION')
|
||||||
#ACCOUNT_EMAIL_VERIFICATION = "none"
|
|
||||||
ACCOUNT_CHANGE_EMAIL = True
|
ACCOUNT_CHANGE_EMAIL = True
|
||||||
ACCOUNT_UNIQUE_EMAIL = True
|
ACCOUNT_UNIQUE_EMAIL = True
|
||||||
ACCOUNT_LOGIN_BY_CODE_ENABLED = True
|
ACCOUNT_LOGIN_BY_CODE_ENABLED = True
|
||||||
|
|
|
||||||
|
|
@ -54,17 +54,6 @@ class HomePageView(TemplateView):
|
||||||
"acceptances"
|
"acceptances"
|
||||||
)
|
)
|
||||||
.select_related("initiated_by__user")
|
.select_related("initiated_by__user")
|
||||||
.annotate(
|
|
||||||
is_active=Case(
|
|
||||||
When(
|
|
||||||
Q(total_have_accepted__lt=F('total_have_quantity')) &
|
|
||||||
Q(total_want_accepted__lt=F('total_want_quantity')),
|
|
||||||
then=Value(True)
|
|
||||||
),
|
|
||||||
default=Value(False),
|
|
||||||
output_field=BooleanField()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
@ -77,7 +66,7 @@ class HomePageView(TemplateView):
|
||||||
.prefetch_related("decks")
|
.prefetch_related("decks")
|
||||||
|
|
||||||
# Reuse base trade offer queryset for market stats
|
# Reuse base trade offer queryset for market stats
|
||||||
base_offer_qs = self.get_base_trade_offer_queryset().filter(manually_closed=False, is_active=True)
|
base_offer_qs = self.get_base_trade_offer_queryset().filter(is_closed=False)
|
||||||
|
|
||||||
# Recent Offers
|
# Recent Offers
|
||||||
recent_offers_qs = base_offer_qs.order_by("-created_at")[:10]
|
recent_offers_qs = base_offer_qs.order_by("-created_at")[:10]
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ django-crispy-forms==2.3
|
||||||
django-daisy==1.0.13
|
django-daisy==1.0.13
|
||||||
django-debug-toolbar==4.4.6
|
django-debug-toolbar==4.4.6
|
||||||
django-el-pagination==4.1.2
|
django-el-pagination==4.1.2
|
||||||
|
django-environ==0.12.0
|
||||||
django-tailwind-4[reload]==0.1.4
|
django-tailwind-4[reload]==0.1.4
|
||||||
django-widget-tweaks==1.5.0
|
django-widget-tweaks==1.5.0
|
||||||
gunicorn==23.0.0
|
gunicorn==23.0.0
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ echo "Waiting for the database to be ready..."
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
echo "Running makemigrations..."
|
echo "Running makemigrations..."
|
||||||
docker compose exec web bash -c "python manage.py makemigrations"
|
uv run python manage.py makemigrations
|
||||||
|
|
||||||
echo "Running migrations..."
|
echo "Running migrations..."
|
||||||
docker compose exec web bash -c "python manage.py migrate"
|
uv run python manage.py migrate
|
||||||
|
|
||||||
echo "Loading seed data..."
|
echo "Loading seed data..."
|
||||||
docker compose exec web bash -c "python manage.py loaddata seed/0*"
|
uv run python manage.py loaddata seed/0*
|
||||||
|
|
||||||
echo "Seeding default friend codes..."
|
echo "Seeding default friend codes..."
|
||||||
docker compose exec web bash -c "python manage.py seed_default_friend_codes"
|
uv run python manage.py seed_default_friend_codes
|
||||||
10
seed/0000_Site.json
Normal file
10
seed/0000_Site.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "sites.site",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"domain": "pkmntrade.club",
|
||||||
|
"name": "PKMN Trade Club"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -3,16 +3,17 @@
|
||||||
"model": "accounts.customuser",
|
"model": "accounts.customuser",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"password": "pbkdf2_sha256$870000$V4NrlZBJxsPJFIeGf2lrc3$tpkNJJtmZ9mE6i2FXE0tdk9MlL/CUmbcERLFgmp+x8s=",
|
"password": "pbkdf2_sha256$870000$f99EReOECF2LPne1JHADoy$pr/769omIyRNkDhaojgIIm8kZcAeFiR0DOLcUvbLlk4=",
|
||||||
"last_login": "2025-02-20T08:53:07.044Z",
|
"last_login": "2025-03-13T04:24:11.029Z",
|
||||||
"is_superuser": true,
|
"is_superuser": true,
|
||||||
"username": "admin",
|
"username": "badblocks",
|
||||||
"first_name": "",
|
"first_name": "",
|
||||||
"last_name": "",
|
"last_name": "",
|
||||||
"email": "rob@badblocks.email",
|
"email": "rob@badblocks.email",
|
||||||
"is_staff": true,
|
"is_staff": true,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"date_joined": "2025-02-20T08:31:16.678Z",
|
"date_joined": "2025-03-13T04:21:04.553Z",
|
||||||
|
"default_friend_code": 1,
|
||||||
"groups": [],
|
"groups": [],
|
||||||
"user_permissions": []
|
"user_permissions": []
|
||||||
}
|
}
|
||||||
|
|
@ -21,18 +22,39 @@
|
||||||
"model": "accounts.customuser",
|
"model": "accounts.customuser",
|
||||||
"pk": 2,
|
"pk": 2,
|
||||||
"fields": {
|
"fields": {
|
||||||
"password": "pbkdf2_sha256$870000$V4NrlZBJxsPJFIeGf2lrc3$tpkNJJtmZ9mE6i2FXE0tdk9MlL/CUmbcERLFgmp+x8s=",
|
"password": "pbkdf2_sha256$870000$NxQDOyPzAvM3FgLL5z0SRy$JuET4f8HI55Oy1umkzg6WtjFVpYTt+UfpZWqPff4EO4=",
|
||||||
"last_login": "2025-02-20T09:01:59.008Z",
|
"last_login": "2025-03-13T04:52:57.949Z",
|
||||||
"is_superuser": false,
|
"is_superuser": false,
|
||||||
"username": "test",
|
"username": "nathanward2016@gmail.com",
|
||||||
"first_name": "",
|
"first_name": "",
|
||||||
"last_name": "",
|
"last_name": "",
|
||||||
"email": "nathanward2016@gmail.com",
|
"email": "nathanward2016@gmail.com",
|
||||||
"is_staff": false,
|
"is_staff": false,
|
||||||
"is_active": true,
|
"is_active": true,
|
||||||
"date_joined": "2025-02-20T09:01:58.289Z",
|
"date_joined": "2025-03-13T04:52:28.482Z",
|
||||||
|
"default_friend_code": 2,
|
||||||
"groups": [],
|
"groups": [],
|
||||||
"user_permissions": []
|
"user_permissions": []
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "accounts.friendcode",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"friend_code": "9167-8051-9691-8032",
|
||||||
|
"user": 1,
|
||||||
|
"created_at": "2025-03-13T04:21:05.166Z",
|
||||||
|
"updated_at": "2025-03-13T04:21:05.166Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "accounts.friendcode",
|
||||||
|
"pk": 2,
|
||||||
|
"fields": {
|
||||||
|
"friend_code": "1234-2938-7848-7636",
|
||||||
|
"user": 2,
|
||||||
|
"created_at": "2025-03-13T04:52:29.166Z",
|
||||||
|
"updated_at": "2025-03-13T04:52:29.166Z"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"model": "accounts.friendcode",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"friend_code": "3595-6375-9151-8459",
|
|
||||||
"user": 1,
|
|
||||||
"created_at": "2025-02-20T08:57:33.268Z",
|
|
||||||
"updated_at": "2025-02-20T08:57:33.268Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "accounts.friendcode",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"friend_code": "4863-0754-2764-1890",
|
|
||||||
"user": 1,
|
|
||||||
"created_at": "2025-02-20T08:58:11.912Z",
|
|
||||||
"updated_at": "2025-02-20T08:58:11.912Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "accounts.friendcode",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"friend_code": "1020-0576-9371-6042",
|
|
||||||
"user": 2,
|
|
||||||
"created_at": "2025-02-20T08:57:33.268Z",
|
|
||||||
"updated_at": "2025-02-20T08:57:33.268Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "accounts.friendcode",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"friend_code": "8358-5883-3807-6654",
|
|
||||||
"user": 2,
|
|
||||||
"created_at": "2025-02-20T08:58:11.912Z",
|
|
||||||
"updated_at": "2025-02-20T08:58:11.912Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
1114
seed/0006_TradeOffers.json
Normal file
1114
seed/0006_TradeOffers.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,112 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "c4ca4238z",
|
|
||||||
"initiated_by": 3,
|
|
||||||
"created_at": "2025-03-07T00:21:33.089Z",
|
|
||||||
"updated_at": "2025-03-07T00:21:33.089Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "c81e728dz",
|
|
||||||
"initiated_by": 4,
|
|
||||||
"created_at": "2025-03-07T00:24:21.664Z",
|
|
||||||
"updated_at": "2025-03-07T00:24:21.664Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "eccbc87ez",
|
|
||||||
"initiated_by": 3,
|
|
||||||
"created_at": "2025-03-07T00:27:36.345Z",
|
|
||||||
"updated_at": "2025-03-07T00:27:36.345Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "a87ff679z",
|
|
||||||
"initiated_by": 4,
|
|
||||||
"created_at": "2025-03-07T00:28:57.655Z",
|
|
||||||
"updated_at": "2025-03-07T00:28:57.655Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "e4da3b7fz",
|
|
||||||
"initiated_by": 4,
|
|
||||||
"created_at": "2025-03-07T00:30:53.491Z",
|
|
||||||
"updated_at": "2025-03-07T00:30:53.491Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "1679091cz",
|
|
||||||
"initiated_by": 1,
|
|
||||||
"created_at": "2025-03-07T00:21:33.089Z",
|
|
||||||
"updated_at": "2025-03-07T00:21:33.089Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "8f14e45fz",
|
|
||||||
"initiated_by": 2,
|
|
||||||
"created_at": "2025-03-07T00:24:21.664Z",
|
|
||||||
"updated_at": "2025-03-07T00:24:21.664Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "c9f0f895z",
|
|
||||||
"initiated_by": 1,
|
|
||||||
"created_at": "2025-03-07T00:27:36.345Z",
|
|
||||||
"updated_at": "2025-03-07T00:27:36.345Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "45c48ccez",
|
|
||||||
"initiated_by": 2,
|
|
||||||
"created_at": "2025-03-07T00:28:57.655Z",
|
|
||||||
"updated_at": "2025-03-07T00:28:57.655Z"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeoffer",
|
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
|
||||||
"manually_closed": false,
|
|
||||||
"hash": "d3d94468z",
|
|
||||||
"initiated_by": 1,
|
|
||||||
"created_at": "2025-03-07T00:30:53.491Z",
|
|
||||||
"updated_at": "2025-03-07T00:30:53.491Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,632 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 113,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 479,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 206,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 414,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 329,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 395,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 42,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 8,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 165,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 65,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 11,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 309,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 12,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 219,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 13,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 413,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 14,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 173,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 15,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 469,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 16,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 424,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 17,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 394,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 18,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 437,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 19,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 384,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 20,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 305,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 21,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 13,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 22,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 177,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 23,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 103,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 24,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 345,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 25,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 76,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 26,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 4,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 27,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 471,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 28,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 379,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 29,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 104,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 30,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 230,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 31,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 529,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 32,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 540,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 33,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 239,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 34,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 248,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 35,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 355,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 36,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 115,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 37,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 502,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 38,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 517,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 39,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 105,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 40,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 151,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 41,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 442,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 42,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 287,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 43,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 194,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 44,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 417,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 45,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 321,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 46,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 34,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 47,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 524,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 48,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 108,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 49,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 30,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 50,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 431,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 51,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 150,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 52,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 210,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 53,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 117,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 54,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 40,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 55,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 486,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 56,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 481,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 57,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 425,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 58,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 300,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 59,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 332,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 60,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 41,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 61,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 84,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 62,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 36,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 63,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 482,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 64,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 401,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 65,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 236,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 66,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 549,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 67,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 227,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 68,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 530,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 69,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 359,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferwantcard",
|
|
||||||
"pk": 70,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 238,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,632 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 115,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 502,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 517,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 105,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 151,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 442,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 287,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 1,
|
|
||||||
"card": 194,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 417,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 321,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 11,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 34,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 12,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 524,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 13,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 108,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 14,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 30,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 15,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 431,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 16,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 2,
|
|
||||||
"card": 150,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 17,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 210,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 18,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 117,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 19,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 40,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 20,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 486,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 21,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 481,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 22,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 425,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 23,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 3,
|
|
||||||
"card": 300,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 24,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 332,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 25,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 41,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 26,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 84,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 27,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 36,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 28,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 482,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 29,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 4,
|
|
||||||
"card": 401,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 30,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 236,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 31,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 549,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 32,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 227,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 33,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 530,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 34,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 359,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 35,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 5,
|
|
||||||
"card": 238,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 36,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 113,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 37,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 479,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 38,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 206,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 39,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 414,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 40,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 329,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 41,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 395,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 42,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 42,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 43,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 6,
|
|
||||||
"card": 8,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 44,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 165,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 45,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 65,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 46,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 309,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 47,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 219,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 48,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 413,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 49,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 173,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 50,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 469,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 51,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 7,
|
|
||||||
"card": 424,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 52,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 394,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 53,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 437,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 54,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 384,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 55,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 305,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 56,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 13,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 57,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 177,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 58,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 8,
|
|
||||||
"card": 103,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 59,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 345,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 60,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 76,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 61,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 4,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 62,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 471,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 63,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 379,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 64,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 9,
|
|
||||||
"card": 104,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 65,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 230,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 66,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 529,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 67,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 540,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 68,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 239,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 69,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 248,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "trades.tradeofferhavecard",
|
|
||||||
"pk": 70,
|
|
||||||
"fields": {
|
|
||||||
"trade_offer": 10,
|
|
||||||
"card": 355,
|
|
||||||
"quantity": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,109 +1,49 @@
|
||||||
/* /* Helper classes
|
.choices.is-disabled .choices__inner,
|
||||||
-------------------------------------------------- */
|
.choices.is-disabled .choices__input {
|
||||||
/* .min-width-fit-content {
|
background-color: var(--color-neutral);
|
||||||
min-width: fit-content;
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* Sticky footer styles
|
|
||||||
-------------------------------------------------- */
|
|
||||||
/* html {
|
|
||||||
position: relative;
|
|
||||||
min-height: 100%;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
@media (min-width: 768px) {
|
.choices[data-type*=select-one] .choices__input {
|
||||||
html {
|
border-bottom: 1px solid var(--btn-shadow);
|
||||||
font-size: 16px;
|
background-color: var(--color-base-100);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.choices[data-type*=select-one] .choices__button:focus {
|
||||||
body {
|
box-shadow: 0 0 0 2px #005F75;
|
||||||
margin-bottom: 60px; /* Margin bottom by footer height
|
|
||||||
}
|
}
|
||||||
|
.choices__inner {
|
||||||
.container {
|
background-color: var(--color-base-100);
|
||||||
max-width: 960px;
|
border: 1px solid var(--btn-shadow);
|
||||||
}
|
}
|
||||||
|
.is-focused .choices__inner, .is-open .choices__inner {
|
||||||
.pricing-header {
|
border-color: var(--btn-shadow);
|
||||||
max-width: 700px;
|
|
||||||
}
|
}
|
||||||
|
.choices__list--multiple .choices__item {
|
||||||
.footer {
|
background-color: var(--color-base-100);
|
||||||
position: absolute;
|
border: 1px solid var(--btn-shadow);
|
||||||
bottom: 0;
|
color: var(--color-primary);
|
||||||
width: 100%;
|
|
||||||
height: 60px; /* Set the fixed height of the footer here
|
|
||||||
line-height: 60px; /* Vertically center the text there
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
} */
|
|
||||||
/* Trade Offer
|
|
||||||
-------------------------------------------------- */
|
|
||||||
.trade-offer-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
}
|
||||||
|
.choices__list--multiple .choices__item.is-highlighted {
|
||||||
.trade-offer-cell {
|
background-color: var(--color-base-100);
|
||||||
display: flex;
|
border: 1px solid var(--btn-shadow);
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
.is-disabled .choices__list--multiple .choices__item {
|
||||||
.trade-offer-header {
|
background-color: var(--color-neutral);
|
||||||
display: flex;
|
border: 1px solid var(--btn-shadow);
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
.choices__list--dropdown, .choices__list[aria-expanded] {
|
||||||
.trade-offer-header .avatar {
|
background-color: var(--color-base-100);
|
||||||
width: 40px;
|
border: 1px solid var(--btn-shadow);
|
||||||
height: 40px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
.is-open .choices__list--dropdown, .is-open .choices__list[aria-expanded] {
|
||||||
.trade-offer-cards {
|
border-color: var(--btn-shadow);
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
.choices__list--dropdown .choices__item--selectable.is-highlighted, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted {
|
||||||
.bg-trade-offer {
|
background-color: var(--color-base-100);
|
||||||
background: linear-gradient(to right, var(--bs-gray-400) 50%, var(--bs-white) 50%);
|
border: 1px solid var(--btn-shadow);
|
||||||
}
|
}
|
||||||
|
.choices__heading {
|
||||||
/* Card Badge
|
border-bottom: 1px solid var(--btn-shadow);
|
||||||
-------------------------------------------------- */
|
color: var(--color-neutral);
|
||||||
/* Responsive: On narrow viewports, stack the Has and Wants sections */
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
.trade-offer-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.choices__input {
|
||||||
/* Fix for the remove item button */
|
background-color: var(--color-base-100);
|
||||||
button.select2-selection__choice__remove {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Trade Offer Card Body with Vertical Separator
|
|
||||||
-------------------------------------------------- */
|
|
||||||
.trade-offer-body {
|
|
||||||
position: relative; /* Required for the absolute separator */
|
|
||||||
background-color: var(--bs-white); /* Use a single background color */
|
|
||||||
}
|
|
||||||
|
|
||||||
.trade-offer-body::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
left: 50%; /* Centered horizontally */
|
|
||||||
transform: translateX(-50%);
|
|
||||||
top: 1rem; /* Gap from the top */
|
|
||||||
bottom: 1rem; /* Gap from the bottom */
|
|
||||||
width: 1px; /* The thickness of the separator */
|
|
||||||
background-color: var(--bs-gray-300); /* Color for the separator */
|
|
||||||
z-index: 1;
|
|
||||||
}
|
}
|
||||||
9
theme/templates/_messages.html
Normal file
9
theme/templates/_messages.html
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{% if messages %}
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} bg-base-100 mb-4">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto max-w-md mt-6 text-center">
|
<div class="container mx-auto max-w-md mt-6 text-center">
|
||||||
<h1 class="text-3xl font-bold mb-6">{% trans "Account Inactive" %}</h1>
|
<h1 class="text-3xl font-bold mb-6">{% trans "Account Inactive" %}</h1>
|
||||||
<p class="text-gray-700">
|
<p>
|
||||||
{% trans "This account is inactive." %}
|
{% trans "This account is inactive." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.code.id_for_label }}" class="block font-medium text-gray-700">{% trans "Verification Code" %}</label>
|
<label for="{{ form.code.id_for_label }}" class="block font-medium>{% trans "Verification Code" %}</label>
|
||||||
{{ form.code }}
|
{{ form.code }}
|
||||||
{{ form.code.errors }}
|
{{ form.code.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.code.id_for_label }}" class="block font-medium text-gray-700">{% trans "Sign-In Code" %}</label>
|
<label for="{{ form.code.id_for_label }}" class="block font-medium>{% trans "Sign-In Code" %}</label>
|
||||||
{{ form.code }}
|
{{ form.code }}
|
||||||
{{ form.code.errors }}
|
{{ form.code.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.code.id_for_label }}" class="block font-medium text-gray-700">{% trans "Reset Code" %}</label>
|
<label for="{{ form.code.id_for_label }}" class="block font-medium>{% trans "Reset Code" %}</label>
|
||||||
{{ form.code }}
|
{{ form.code }}
|
||||||
{{ form.code.errors }}
|
{{ form.code.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.code.id_for_label }}" class="block font-medium text-gray-700">{% trans "Verification Code" %}</label>
|
<label for="{{ form.code.id_for_label }}" class="block font-medium>{% trans "Verification Code" %}</label>
|
||||||
{{ form.code }}
|
{{ form.code }}
|
||||||
{{ form.code.errors }}
|
{{ form.code.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Email Addresses" %}</h1>
|
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Email Addresses" %}</h1>
|
||||||
|
|
||||||
{% if emailaddresses %}
|
{% if emailaddresses %}
|
||||||
<p class="text-gray-700 mb-4">
|
<p class="mb-4">
|
||||||
{% trans "The following email addresses are associated with your account:" %}
|
{% trans "The following email addresses are associated with your account:" %}
|
||||||
</p>
|
</p>
|
||||||
{% url 'account_email' as email_url %}
|
{% url 'account_email' as email_url %}
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
<form method="post" action="{{ action_url }}" class="space-y-4">
|
<form method="post" action="{{ action_url }}" class="space-y-4">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.email.id_for_label }}" class="block font-medium text-gray-700">{{ form.email.label }}</label>
|
<label for="{{ form.email.id_for_label }}" class="block font-medium>{{ form.email.label }}</label>
|
||||||
{{ form.email }}
|
{{ form.email }}
|
||||||
{{ form.email.errors }}
|
{{ form.email.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if current_emailaddress %}
|
{% if current_emailaddress %}
|
||||||
<div>
|
<div>
|
||||||
<label for="current_email" class="block font-medium text-gray-700">{% trans "Current email" %}:</label>
|
<label for="current_email" class="block font-medium>{% trans "Current email" %}:</label>
|
||||||
<input id="current_email" type="email" value="{{ current_emailaddress.email }}" disabled class="input input-bordered w-full">
|
<input id="current_email" type="email" value="{{ current_emailaddress.email }}" disabled class="input input-bordered w-full">
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if new_emailaddress %}
|
{% if new_emailaddress %}
|
||||||
<div>
|
<div>
|
||||||
<label for="new_email" class="block font-medium text-gray-700">
|
<label for="new_email" class="block font-medium>
|
||||||
{% if not current_emailaddress %}
|
{% if not current_emailaddress %}
|
||||||
{% trans "Current email" %}:
|
{% trans "Current email" %}:
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.email.id_for_label }}" class="block font-medium text-gray-700">{% trans "Change to" %}:</label>
|
<label for="{{ form.email.id_for_label }}" class="block font-medium>{% trans "Change to" %}:</label>
|
||||||
{{ form.email }}
|
{{ form.email }}
|
||||||
{{ form.email.errors }}
|
{{ form.email.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
{% if confirmation %}
|
{% if confirmation %}
|
||||||
{% user_display confirmation.email_address.user as user_display %}
|
{% user_display confirmation.email_address.user as user_display %}
|
||||||
{% if can_confirm %}
|
{% if can_confirm %}
|
||||||
<p class="text-gray-700 mb-4">
|
<p class="mb-4">
|
||||||
{% blocktrans with confirmation.email_address.email as email %}
|
{% blocktrans with confirmation.email_address.email as email %}
|
||||||
Please confirm that <a class="text-primary underline" href="mailto:{{ email }}">{{ email }}</a> is an email address for user {{ user_display }}.
|
Please confirm that <a class="text-primary underline" href="mailto:{{ email }}">{{ email }}</a> is an email address for user {{ user_display }}.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
|
@ -22,13 +22,13 @@
|
||||||
<button type="submit" class="btn btn-primary w-full">{% trans "Confirm" %}</button>
|
<button type="submit" class="btn btn-primary w-full">{% trans "Confirm" %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-gray-700">
|
<p>
|
||||||
{% blocktrans %}Unable to confirm {{ confirmation.email_address.email }} because it is already confirmed by a different account.{% endblocktrans %}
|
{% blocktrans %}Unable to confirm {{ confirmation.email_address.email }} because it is already confirmed by a different account.{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% url 'account_email' as email_url %}
|
{% url 'account_email' as email_url %}
|
||||||
<p class="text-gray-700">
|
<p>
|
||||||
{% blocktrans %}This email confirmation link expired or is invalid. Please <a class="text-primary underline" href="{{ email_url }}">issue a new email confirmation request</a>.{% endblocktrans %}
|
{% blocktrans %}This email confirmation link expired or is invalid. Please <a class="text-primary underline" href="{{ email_url }}">issue a new email confirmation request</a>.{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.login.id_for_label }}" class="block font-medium text-gray-700">{{ form.login.label }}</label>
|
<label for="{{ form.login.id_for_label }}" class="block font-medium">{{ form.login.label }}</label>
|
||||||
{{ form.login }}
|
{{ form.login }}
|
||||||
{{ form.login.errors }}
|
{{ form.login.errors }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.password.id_for_label }}" class="block font-medium text-gray-700">{{ form.password.label }}</label>
|
<label for="{{ form.password.id_for_label }}" class="block font-medium">{{ form.password.label }}</label>
|
||||||
{{ form.password }}
|
{{ form.password }}
|
||||||
{{ form.password.errors }}
|
{{ form.password.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.email.id_for_label }}" class="block font-medium text-gray-700">{{ form.email.label }}</label>
|
<label for="{{ form.email.id_for_label }}" class="block font-medium>{{ form.email.label }}</label>
|
||||||
{{ form.email|add_class:"input input-bordered w-full" }}
|
{{ form.email|add_class:"input input-bordered w-full" }}
|
||||||
{{ form.email.errors }}
|
{{ form.email.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto max-w-md mt-6">
|
<div class="container mx-auto max-w-md mt-6">
|
||||||
<p class="text-gray-700 mb-4">{% trans "Enter your password:" %}</p>
|
<p class="mb-4">{% trans "Enter your password:" %}</p>
|
||||||
{% url 'account_reauthenticate' as action_url %}
|
{% url 'account_reauthenticate' as action_url %}
|
||||||
<form method="post" action="{{ action_url }}" class="space-y-4">
|
<form method="post" action="{{ action_url }}" class="space-y-4">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ field.id_for_label }}" class="block font-medium text-gray-700">{{ field.label }}</label>
|
<label for="{{ field.id_for_label }}" class="block font-medium>{{ field.label }}</label>
|
||||||
{{ field }}
|
{{ field }}
|
||||||
{{ field.errors }}
|
{{ field.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto max-w-md mt-6">
|
<div class="container mx-auto max-w-md mt-6">
|
||||||
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Send me a sign-in code" %}</h1>
|
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Send me a sign-in code" %}</h1>
|
||||||
<p class="text-gray-700 mb-4 text-center">
|
<p class="mb-4 text-center">
|
||||||
{% trans "You will receive a special code for a password-free sign-in." %}
|
{% trans "You will receive a special code for a password-free sign-in." %}
|
||||||
</p>
|
</p>
|
||||||
{% url 'account_request_login_code' as login_url %}
|
{% url 'account_request_login_code' as login_url %}
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ field.id_for_label }}" class="block font-medium text-gray-700">{{ field.label }}</label>
|
<label for="{{ field.id_for_label }}" class="block font-medium>{{ field.label }}</label>
|
||||||
{{ field }}
|
{{ field }}
|
||||||
{{ field.errors }}
|
{{ field.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,27 +10,27 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.username.id_for_label }}" class="block font-medium text-gray-700">{{ form.username.label }}</label>
|
<label for="{{ form.username.id_for_label }}" class="block font-medium">{{ form.username.label }}</label>
|
||||||
{{ form.username }}
|
{{ form.username }}
|
||||||
{{ form.username.errors }}
|
{{ form.username.errors }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.email.id_for_label }}" class="block font-medium text-gray-700">{{ form.email.label }}</label>
|
<label for="{{ form.email.id_for_label }}" class="block font-medium">{{ form.email.label }}</label>
|
||||||
{{ form.email }}
|
{{ form.email }}
|
||||||
{{ form.email.errors }}
|
{{ form.email.errors }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.password1.id_for_label }}" class="block font-medium text-gray-700">{{ form.password1.label }}</label>
|
<label for="{{ form.password1.id_for_label }}" class="block font-medium">{{ form.password1.label }}</label>
|
||||||
{{ form.password1 }}
|
{{ form.password1 }}
|
||||||
{{ form.password1.errors }}
|
{{ form.password1.errors }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.password2.id_for_label }}" class="block font-medium text-gray-700">{{ form.password2.label }}</label>
|
<label for="{{ form.password2.id_for_label }}" class="block font-medium">{{ form.password2.label }}</label>
|
||||||
{{ form.password2 }}
|
{{ form.password2 }}
|
||||||
{{ form.password2.errors }}
|
{{ form.password2.errors }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ form.friend_code.id_for_label }}" class="block font-medium text-gray-700">{{ form.friend_code.label }}</label>
|
<label for="{{ form.friend_code.id_for_label }}" class="block font-medium">{{ form.friend_code.label }}</label>
|
||||||
{{ form.friend_code }}
|
{{ form.friend_code }}
|
||||||
{{ form.friend_code.errors }}
|
{{ form.friend_code.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto max-w-md mt-6">
|
<div class="container mx-auto max-w-md mt-6">
|
||||||
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Passkey Sign Up" %}</h1>
|
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Passkey Sign Up" %}</h1>
|
||||||
<p class="text-gray-700 mb-4 text-center">
|
<p class="mb-4 text-center">
|
||||||
{% blocktranslate %}
|
{% blocktranslate %}
|
||||||
Already have an account? Then please <a href="{{ login_url }}" class="text-primary underline">{% trans "sign in" %}</a>.
|
Already have an account? Then please <a href="{{ login_url }}" class="text-primary underline">{% trans "sign in" %}</a>.
|
||||||
{% endblocktranslate %}
|
{% endblocktranslate %}
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
{{ form.non_field_errors }}
|
{{ form.non_field_errors }}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div>
|
<div>
|
||||||
<label for="{{ field.id_for_label }}" class="block font-medium text-gray-700">{{ field.label }}</label>
|
<label for="{{ field.id_for_label }}" class="block font-medium>{{ field.label }}</label>
|
||||||
{{ field }}
|
{{ field }}
|
||||||
{{ field.errors }}
|
{{ field.errors }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto max-w-md mt-6 text-center">
|
<div class="container mx-auto max-w-md mt-6 text-center">
|
||||||
<h1 class="text-3xl font-bold mb-6">{% trans "Sign Up Closed" %}</h1>
|
<h1 class="text-3xl font-bold mb-6">{% trans "Sign Up Closed" %}</h1>
|
||||||
<p class="text-gray-700">
|
<p>
|
||||||
{% trans "We are sorry, but the sign up is currently closed." %}
|
{% trans "We are sorry, but the sign up is currently closed." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto max-w-md mt-6">
|
<div class="container mx-auto max-w-md mt-6">
|
||||||
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Verify Your Email Address" %}</h1>
|
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Verify Your Email Address" %}</h1>
|
||||||
<p class="text-gray-700">
|
<p>
|
||||||
{% blocktrans %}
|
{% blocktrans %}
|
||||||
We have sent an email to you for verification. Follow the link provided to finalize the signup process. If you do not see the verification email in your main inbox, check your spam folder. Please contact us if you do not receive the verification email within a few minutes.
|
We have sent an email to you for verification. Follow the link provided to finalize the signup process. If you do not see the verification email in your main inbox, check your spam folder. Please contact us if you do not receive the verification email within a few minutes.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,17 @@
|
||||||
<div class="container mx-auto max-w-md mt-6">
|
<div class="container mx-auto max-w-md mt-6">
|
||||||
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Verify Your Email Address" %}</h1>
|
<h1 class="text-3xl font-bold text-center mb-6">{% trans "Verify Your Email Address" %}</h1>
|
||||||
{% url 'account_email' as email_url %}
|
{% url 'account_email' as email_url %}
|
||||||
<p class="text-gray-700 mb-4">
|
<p class="mb-4">
|
||||||
{% blocktrans %}
|
{% blocktrans %}
|
||||||
This part of the site requires us to verify that you are who you claim to be. For this purpose, we require that you verify ownership of your email address.
|
This part of the site requires us to verify that you are who you claim to be. For this purpose, we require that you verify ownership of your email address.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700 mb-4">
|
<p class="mb-4">
|
||||||
{% blocktrans %}
|
{% blocktrans %}
|
||||||
We have sent an email to you for verification. Please click on the link inside that email. If you do not see the verification email in your main inbox, check your spam folder. Otherwise contact us if you do not receive it within a few minutes.
|
We have sent an email to you for verification. Please click on the link inside that email. If you do not see the verification email in your main inbox, check your spam folder. Otherwise contact us if you do not receive it within a few minutes.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-700">
|
<p>
|
||||||
{% blocktrans %}
|
{% blocktrans %}
|
||||||
<strong>Note:</strong> you can still <a class="text-primary underline" href="{{ email_url }}">change your email address</a>.
|
<strong>Note:</strong> you can still <a class="text-primary underline" href="{{ email_url }}">change your email address</a>.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
{% load static tailwind_tags gravatar %}
|
{% load static tailwind_tags gravatar %}
|
||||||
|
{% url 'home' as home_url %}
|
||||||
|
{% url 'trade_offer_list' as trade_offer_list_url %}
|
||||||
|
{% url 'trade_offer_my_list' as trade_offer_my_list_url %}
|
||||||
|
{% url 'settings' as settings_url %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="light">
|
<html lang="en" data-theme="light">
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -19,7 +24,7 @@
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<title>{% block title %}Pkmn Trade Club{% endblock title %}</title>
|
<title>{% block title %}PᴋMɴ Trade Club{% endblock title %}</title>
|
||||||
<link rel="shortcut icon" href="{% static 'images/favicon.ico' %}">
|
<link rel="shortcut icon" href="{% static 'images/favicon.ico' %}">
|
||||||
<!-- Choices.js -->
|
<!-- Choices.js -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js@11.0.6/public/assets/styles/choices.min.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js@11.0.6/public/assets/styles/choices.min.css" />
|
||||||
|
|
@ -57,12 +62,9 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-ghost text-xl" href="{% url 'home' %}">
|
<a class="btn btn-ghost text-xl" href="{% url 'home' %}">
|
||||||
<span aria-hidden="true">
|
<span class="inline leading-none" aria-hidden="true">
|
||||||
<sup class="inline-block relative left-2">P</sup>
|
<span class="inline-block relative align-text-top">P</span><span class="inline-block relative align-text-bottom">K</span><span class="inline-block relative align-text-top">M</span><span class="inline-block relative align-text-bottom">N</span>
|
||||||
<sub class="inline-block relative">K</sub>
|
<span class="inline-block relative">Trade Club</span>
|
||||||
<sup class="inline-block relative -left-2">M</sup>
|
|
||||||
<sub class="inline-block relative -left-4">N</sub>
|
|
||||||
<span class="inline-block relative -left-4">Trade Club</span>
|
|
||||||
</span>
|
</span>
|
||||||
<span aria-hidden="false" class="sr-only">Pokemon Trade Club</span>
|
<span aria-hidden="false" class="sr-only">Pokemon Trade Club</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -126,6 +128,7 @@
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="container mx-auto p-4 sm:w-4/5 md:w-full xl:w-256">
|
<main class="container mx-auto p-4 sm:w-4/5 md:w-full xl:w-256">
|
||||||
|
{% include '_messages.html' %}
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
@ -138,19 +141,19 @@
|
||||||
|
|
||||||
<!-- Dock -->
|
<!-- Dock -->
|
||||||
<div x-data class="dock bg-neutral text-neutral-content sm:hidden">
|
<div x-data class="dock bg-neutral text-neutral-content sm:hidden">
|
||||||
<button @click="window.location.href = '{% url 'home' %}'" class="{% if request.path == '/' %}dock-active{% endif %}">
|
<button @click="window.location.href = '{{ home_url }}'" class="{% if request.path == home_url %}dock-active{% endif %}">
|
||||||
<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="currentColor" stroke-linejoin="miter" stroke-linecap="butt"><polyline points="1 11 12 2 23 11" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="2"></polyline><path d="m5,13v7c0,1.105.895,2,2,2h10c1.105,0,2-.895,2-2v-7" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></path><line x1="12" y1="22" x2="12" y2="18" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></line></g></svg>
|
<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="currentColor" stroke-linejoin="miter" stroke-linecap="butt"><polyline points="1 11 12 2 23 11" fill="none" stroke="currentColor" stroke-miterlimit="10" stroke-width="2"></polyline><path d="m5,13v7c0,1.105.895,2,2,2h10c1.105,0,2-.895,2-2v-7" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></path><line x1="12" y1="22" x2="12" y2="18" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></line></g></svg>
|
||||||
<span class="dock-label">Home</span>
|
<span class="dock-label">Home</span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="window.location.href = '{% url 'trade_offer_list' %}'" class="{% if '/trades/all/' in request.path %}dock-active{% endif %}">
|
<button @click="window.location.href = '{{ trade_offer_list_url }}'" class="{% if request.path == trade_offer_list_url %}dock-active{% endif %}">
|
||||||
<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 0 1 0 3.75H5.625a1.875 1.875 0 0 1 0-3.75Z" /></svg>
|
<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 0 1 0 3.75H5.625a1.875 1.875 0 0 1 0-3.75Z" /></svg>
|
||||||
<span class="dock-label">All Offers</span>
|
<span class="dock-label">All Offers</span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="window.location.href = '{% url 'trade_offer_my_list' %}'" class="{% if '/trades/my/' in request.path %}dock-active{% endif %}">
|
<button @click="window.location.href = '{{ trade_offer_my_list_url }}'" class="{% if request.path == trade_offer_my_list_url %}dock-active{% endif %}">
|
||||||
<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3 7.5 7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5" /></svg>
|
<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3 7.5 7.5 3m0 0L12 7.5M7.5 3v13.5m13.5 0L16.5 21m0 0L12 16.5m4.5 4.5V7.5" /></svg>
|
||||||
<span class="dock-label">My Trades</span>
|
<span class="dock-label">My Trades</span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="window.location.href = '{% url 'settings' %}'" class="{% if '/settings/' in request.path %}dock-active{% endif %}">
|
<button @click="window.location.href = '{{ settings_url }}'" class="{% if request.path == settings_url %}dock-active{% endif %}">
|
||||||
{% if user.is_authenticated %}<div tabindex="0" role="button" class="avatar"><div class="w-6 rounded-full">{{ user.email|gravatar:40 }}</div></div>{% else %}<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="currentColor" stroke-linejoin="miter" stroke-linecap="butt"><circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></circle><path d="m22,13.25v-2.5l-2.318-.966c-.167-.581-.395-1.135-.682-1.654l.954-2.318-1.768-1.768-2.318.954c-.518-.287-1.073-.515-1.654-.682l-.966-2.318h-2.5l-.966,2.318c-.581.167-1.135.395-1.654.682l-2.318-.954-1.768,1.768.954,2.318c-.287.518-.515,1.073-.682,1.654l-2.318.966v2.5l2.318.966c.167.581.395,1.135.682,1.654l-.954,2.318,1.768,1.768,2.318-.954c.518.287,1.073.515,1.654.682l.966,2.318h2.5l.966-2.318c.581-.167,1.135-.395,1.654-.682l2.318.954,1.768-1.768-.954-2.318c.287-.518.515-1.073.682-1.654l2.318-.966Z" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></path></g></svg>{% endif %}
|
{% if user.is_authenticated %}<div tabindex="0" role="button" class="avatar"><div class="w-6 rounded-full">{{ user.email|gravatar:40 }}</div></div>{% else %}<svg class="size-[1.2em]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="currentColor" stroke-linejoin="miter" stroke-linecap="butt"><circle cx="12" cy="12" r="3" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></circle><path d="m22,13.25v-2.5l-2.318-.966c-.167-.581-.395-1.135-.682-1.654l.954-2.318-1.768-1.768-2.318.954c-.518-.287-1.073-.515-1.654-.682l-.966-2.318h-2.5l-.966,2.318c-.581.167-1.135.395-1.654.682l-2.318-.954-1.768,1.768.954,2.318c-.287.518-.515,1.073-.682,1.654l-2.318.966v2.5l2.318.966c.167.581.395,1.135.682,1.654l-.954,2.318,1.768,1.768,2.318-.954c.518.287,1.073.515,1.654.682l.966,2.318h2.5l.966-2.318c.581-.167,1.135-.395,1.654-.682l2.318.954,1.768-1.768-.954-2.318c.287-.518.515-1.073.682-1.654l2.318-.966Z" fill="none" stroke="currentColor" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2"></path></g></svg>{% endif %}
|
||||||
<span class="dock-label">Settings</span>
|
<span class="dock-label">Settings</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -4,31 +4,20 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto max-w-xl mt-6">
|
<div class="container mx-auto max-w-xl mt-6">
|
||||||
{# Display messages if there are any. #}
|
<h1 class="text-3xl font-bold mb-4">My Friend Codes</h1>
|
||||||
{% if messages %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert alert-{{ message.tags }} mb-4">
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<h1 class="text-3xl font-bold mb-4">My Friend Codes</h1>
|
|
||||||
|
|
||||||
{% if friend_codes %}
|
{% if friend_codes %}
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
{% for code in friend_codes %}
|
{% for code in friend_codes %}
|
||||||
<li class="flex items-center justify-between {% if user.default_friend_code and code.id == user.default_friend_code.id %}bg-green-100{% else %}bg-base-100{% endif %} p-4 rounded shadow">
|
<li class="flex items-center justify-between {% if user.default_friend_code and code.id == user.default_friend_code.id %}bg-green-200 dark:bg-green-300 dark:text-base-100{% else %}bg-base-100 dark:bg-base-900 dark:text-white{% endif %} p-4 rounded shadow">
|
||||||
<div>
|
<div>
|
||||||
<span class="font-mono dark:text-base-100">{{ code.friend_code }}</span>
|
<span class="font-mono">{{ code.friend_code }}</span>
|
||||||
{% if user.default_friend_code and code.id == user.default_friend_code.id %}
|
{% if user.default_friend_code and code.id == user.default_friend_code.id %}
|
||||||
<span class="badge badge-success ml-2">Default</span>
|
<span class="badge badge-success ml-2">Default</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
{% if user.default_friend_code and code.id == user.default_friend_code.id %}
|
{% if user.default_friend_code and not code.id == user.default_friend_code.id %}
|
||||||
<button type="button" class="btn btn-secondary btn-sm" disabled>Set as Default</button>
|
|
||||||
{% else %}
|
|
||||||
<form method="post" action="{% url 'change_default_friend_code' code.id %}">
|
<form method="post" action="{% url 'change_default_friend_code' code.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-secondary btn-sm">Set as Default</button>
|
<button type="submit" class="btn btn-secondary btn-sm">Set as Default</button>
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,10 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 class="text-center text-4xl font-bold mb-8 pt-4">
|
<h1 class="text-center text-4xl font-bold mb-8 pt-4">
|
||||||
<span aria-hidden="true">
|
<span class="inline leading-none" aria-hidden="true">
|
||||||
<span class="inline-block relative left-2 text-4xl">Welcome to</span>
|
<span class="inline-block relative">Welcome to</span>
|
||||||
<sup class="inline-block relative left-4 text-4xl">P</sup>
|
<span class="inline-block relative align-text-top">P</span><span class="inline-block relative align-text-bottom">K</span><span class="inline-block relative align-text-top">M</span><span class="inline-block relative align-text-bottom">N</span>
|
||||||
<sub class="inline-block relative text-4xl">K</sub>
|
<span class="inline-block relative">Trade Club</span>
|
||||||
<sup class="inline-block relative -left-2 text-4xl">M</sup>
|
|
||||||
<sub class="inline-block relative -left-4 text-4xl">N</sub>
|
|
||||||
<span class="inline-block relative -left-2 text-4xl">Trade Club</span>
|
|
||||||
</span>
|
</span>
|
||||||
<span aria-hidden="false" class="sr-only">Welcome to Pokemon Trade Club</span>
|
<span aria-hidden="false" class="sr-only">Welcome to Pokemon Trade Club</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
@ -20,18 +17,18 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
{% card_multiselect "have_cards" "Have:" "Select some cards..." available_cards have_cards %}
|
{% card_multiselect "have_cards" "I Have:" "Select some cards..." available_cards have_cards %}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{% card_multiselect "want_cards" "Want:" "Select some cards..." available_cards want_cards %}
|
{% card_multiselect "want_cards" "I Want:" "Select some cards..." available_cards want_cards %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col md:flex-row gap-4">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
<button type="submit" class="btn btn-primary flex-1">
|
<button type="submit" class="btn btn-primary grow">
|
||||||
Find a Trade Offer
|
Find a Trade Offer
|
||||||
</button>
|
</button>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="{% url 'trade_offer_create' %}" id="createTradeOfferBtn" class="btn btn-secondary flex-1 text-center">
|
<a href="{% url 'trade_offer_create' %}" id="createTradeOfferBtn" class="btn btn-secondary grow">
|
||||||
Create Trade Offer
|
Create Trade Offer
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,23 @@
|
||||||
This fragment renders a friend code selector used for filtering or form submissions.
|
This fragment renders a friend code selector used for filtering or form submissions.
|
||||||
Expected variables:
|
Expected variables:
|
||||||
- friend_codes: A list or QuerySet of FriendCode objects.
|
- friend_codes: A list or QuerySet of FriendCode objects.
|
||||||
- selected_friend_code: The currently selected FriendCode.
|
- selected_friend_code (optional): The currently selected FriendCode. If not provided, the user's default friend code is used.
|
||||||
- field_name (optional): The name/id for the input element (default "friend_code").
|
- field_name (optional): The name/id for the input element (default "friend_code").
|
||||||
- label (optional): The label text (default "Friend Code").
|
- label (optional): The label text (default "Friend Code").
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% with field_name=field_name|default:"friend_code" label=label|default:"Friend Code" %}
|
{% with field_name=field_name|default:"friend_code" label=label|default:"" %}
|
||||||
|
{% with effective_friend_code=selected_friend_code|default:request.user.default_friend_code %}
|
||||||
{% if friend_codes|length > 1 %}
|
{% if friend_codes|length > 1 %}
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
|
{% if label and label != "" %}
|
||||||
<label for="{{ field_name }}" class="label">
|
<label for="{{ field_name }}" class="label">
|
||||||
<span class="label-text p-2 rounded">{{ label }}</span>
|
<span class="label-text p-2 rounded">{{ label }}</span>
|
||||||
</label>
|
</label>
|
||||||
<select id="{{ field_name }}" name="{{ field_name }}" class="select select-bordered w-full bg-secondary text-white">
|
{% endif %}
|
||||||
|
<select id="{{ field_name }}" name="{{ field_name }}" class="select select-bordered w-full" @change="$el.form.submit()">
|
||||||
{% for code in friend_codes %}
|
{% for code in friend_codes %}
|
||||||
<option value="{{ code.pk }}" {% if code.pk|stringformat:"s" == selected_friend_code.pk|stringformat:"s" %}selected{% endif %}>
|
<option value="{{ code.pk }}" {% if effective_friend_code and code.pk|stringformat:"s" == effective_friend_code.pk|stringformat:"s" %}selected{% endif %}>
|
||||||
{{ code.friend_code }}
|
{{ code.friend_code }}
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
@ -24,4 +27,5 @@ Expected variables:
|
||||||
{% else %}
|
{% else %}
|
||||||
<input type="hidden" name="{{ field_name }}" value="{{ friend_codes.0.pk }}">
|
<input type="hidden" name="{{ field_name }}" value="{{ friend_codes.0.pk }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
@ -5,23 +5,21 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto max-w-4xl mt-6" x-data="{ allExpanded: false }">
|
<div class="container mx-auto max-w-4xl mt-6" x-data="{ allExpanded: false }">
|
||||||
|
<!-- Header-->
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<a href="{% url 'trade_offer_create' %}" class="btn btn-success">Create New Offer</a>
|
||||||
<div>
|
<div>
|
||||||
<form method="get" class="flex items-center space-x-4">
|
<form method="get" class="flex items-center space-x-4" x-data>
|
||||||
<label class="cursor-pointer flex items-center space-x-2">
|
<label class="cursor-pointer flex items-center space-x-2">
|
||||||
<span class="font-medium">Only Closed</span>
|
|
||||||
<input type="checkbox" name="show_closed" value="true" class="toggle toggle-primary" {% if show_closed %}checked{% endif %}>
|
|
||||||
</label>
|
|
||||||
<button type="submit" class="btn btn-primary">Apply</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<!-- Global toggle button using Alpine only -->
|
|
||||||
<button type="button"
|
|
||||||
@click="allExpanded = !allExpanded; $dispatch('toggle-all', { expanded: allExpanded })"
|
|
||||||
class="btn btn-secondary">
|
|
||||||
<span x-text="allExpanded ? 'Collapse All' : 'Expand All'"></span>
|
<span x-text="allExpanded ? 'Collapse All' : 'Expand All'"></span>
|
||||||
</button>
|
<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">
|
||||||
|
<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>
|
||||||
|
<button type="submit" class="btn btn-primary" x-show="false">Apply</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Trade Offers -->
|
<!-- Trade Offers -->
|
||||||
|
|
@ -46,9 +44,5 @@
|
||||||
<p>No trade offers found.</p>
|
<p>No trade offers found.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
<a href="{% url 'trade_offer_create' %}" class="btn btn-success">Create New Offer</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
@ -9,20 +9,19 @@
|
||||||
<form method="post" novalidate class="space-y-4">
|
<form method="post" novalidate class="space-y-4">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{# Use our DRY friend code selector #}
|
|
||||||
{% include "trades/_friend_code_select.html" with friend_codes=friend_codes selected_friend_code=selected_friend_code field_name=form.initiated_by.html_name label="Initiated by" %}
|
{% include "trades/_friend_code_select.html" with friend_codes=friend_codes selected_friend_code=selected_friend_code field_name=form.initiated_by.html_name label="Initiated by" %}
|
||||||
|
|
||||||
<!-- Card Selectors: "Have" and "Want" -->
|
<!-- Card Selectors: "Have" and "Want" -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
{% card_multiselect "have_cards" "Have:" "Select one or more cards..." available_cards form.initial.have_cards %}
|
{% card_multiselect "have_cards" "I Have:" "Select some cards..." available_cards form.initial.have_cards %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
{% card_multiselect "want_cards" "Want:" "Select one or more cards..." available_cards form.initial.want_cards %}
|
{% card_multiselect "want_cards" "I Want:" "Select some cards..." available_cards form.initial.want_cards %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary w-full">Submit</button>
|
<button type="submit" class="btn btn-primary w-full">Create Offer</button>
|
||||||
</form>
|
</form>
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<div class="alert alert-error mt-4">
|
<div class="alert alert-error mt-4">
|
||||||
|
|
@ -40,36 +39,4 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<script defer>
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const initiatedBySelect = document.getElementById('{{ form.initiated_by.html_name }}');
|
|
||||||
if (initiatedBySelect) {
|
|
||||||
const choicesInstance = new Choices(initiatedBySelect, {
|
|
||||||
searchEnabled: false,
|
|
||||||
classNames: {
|
|
||||||
containerOuter: 'choices',
|
|
||||||
containerInner: 'choices__inner',
|
|
||||||
input: 'choices__input',
|
|
||||||
},
|
|
||||||
callbackOnCreateTemplates: function(template) {
|
|
||||||
return {
|
|
||||||
choice: (classNames, data) => {
|
|
||||||
return template(`
|
|
||||||
<div class="${classNames.item} ${classNames.itemChoice} bg-accent text-white"
|
|
||||||
data-select-text="${this.config.itemSelectText}"
|
|
||||||
data-choice ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'}
|
|
||||||
data-id="${data.id}" data-value="${data.value}"
|
|
||||||
${data.groupId > 0 ? 'role="treeitem"' : 'role="option"'}>
|
|
||||||
${data.label}
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
choicesInstance.containerOuter.element.classList.add('bg-secondary', 'select', 'select-bordered', 'w-full');
|
|
||||||
choicesInstance.containerInner.element.classList.add('bg-secondary', 'text-white');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
@ -15,15 +15,9 @@
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<strong>Status:</strong> {% if object.is_active %}Open{% else %}Closed{% endif %}
|
<strong>Status:</strong> {% if object.is_closed %}Closed{% else %}Open{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if messages %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert {{ message.tags }}">{{ message }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
{% if action == 'delete' %}
|
{% if action == 'delete' %}
|
||||||
Are you sure you want to delete this trade offer? This will permanently remove the offer.
|
Are you sure you want to delete this trade offer? This will permanently remove the offer.
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,32 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}Trade Offer & Acceptance List{% endblock title %}
|
{% block title %}My Trades{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto max-w-4xl mt-6" x-data="{ allExpanded: false }">
|
<div class="container mx-auto max-w-4xl mt-6" x-data="{ allExpanded: false }">
|
||||||
<!-- Global Header: Filter Form and Expand All Toggle -->
|
<!-- Header -->
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-start mb-4">
|
||||||
|
<a href="{% url 'trade_offer_create' %}" class="btn btn-success">Create New Offer</a>
|
||||||
<div>
|
<div>
|
||||||
<form method="get" class="flex items-center space-x-4">
|
<form method="get" class="flex flex-wrap justify-end space-x-4 gap-2" x-data>
|
||||||
{% include "trades/_friend_code_select.html" with friend_codes=friend_codes selected_friend_code=selected_friend_code field_name="friend_code" label="Filter by Friend Code" %}
|
<label class="cursor-pointer flex items-center space-x-2 h-10">
|
||||||
|
<span x-text="allExpanded ? 'Collapse All' : 'Expand All'"></span>
|
||||||
<label class="cursor-pointer flex items-center space-x-2">
|
<input type="checkbox" name="all_expanded" value="true" class="toggle toggle-primary" @click="allExpanded = !allExpanded; $dispatch('toggle-all', { expanded: allExpanded })">
|
||||||
<span class="font-medium">Only Closed</span>
|
|
||||||
<input type="checkbox" name="show_closed" value="true" class="toggle toggle-primary" {% if show_closed %}checked{% endif %}>
|
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" class="btn btn-primary">Apply</button>
|
<label class="cursor-pointer flex items-center space-x-2 h-10">
|
||||||
|
<span class="font-medium">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>
|
||||||
|
|
||||||
|
{% include "trades/_friend_code_select.html" with friend_codes=friend_codes selected_friend_code=selected_friend_code field_name="friend_code" label="" %}
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary" x-show="false">Apply</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<!-- Global toggle button using Alpine only -->
|
|
||||||
<button type="button"
|
|
||||||
@click="allExpanded = !allExpanded; $dispatch('toggle-all', { expanded: allExpanded })"
|
|
||||||
class="btn btn-secondary">
|
|
||||||
<span x-text="allExpanded ? 'Collapse All' : 'Expand All'"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-8">
|
||||||
<!-- Section: Waiting for Your Response -->
|
<!-- Section: Waiting for Your Response -->
|
||||||
<section class="mb-12">
|
<section class="mb-12">
|
||||||
<h2 class="text-2xl font-bold mb-4">Waiting for Your Response</h2>
|
<h2 class="text-2xl font-bold mb-4">Waiting for Your Response</h2>
|
||||||
|
|
@ -51,9 +50,9 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Section: Waiting for Trade Partner's Response -->
|
<!-- Section: Waiting for Their Response -->
|
||||||
<section class="mb-12">
|
<section class="mb-12">
|
||||||
<h2 class="text-2xl font-bold mb-4">Waiting for Trade Partner's Response</h2>
|
<h2 class="text-2xl font-bold mb-4">Waiting for Their Response</h2>
|
||||||
{% if other_party_trade_acceptances_paginated.object_list %}
|
{% if other_party_trade_acceptances_paginated.object_list %}
|
||||||
{% include "trades/_trade_offer_list.html" with offers=other_party_trade_acceptances_paginated %}
|
{% include "trades/_trade_offer_list.html" with offers=other_party_trade_acceptances_paginated %}
|
||||||
<div class="flex justify-between items-center mt-4">
|
<div class="flex justify-between items-center mt-4">
|
||||||
|
|
@ -73,7 +72,7 @@
|
||||||
<p>None at this time.</p>
|
<p>None at this time.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
<!-- Section: My Trade Offers -->
|
<!-- Section: My Trade Offers -->
|
||||||
<section class="mb-12">
|
<section class="mb-12">
|
||||||
<h2 class="text-2xl font-bold mb-4">My Trade Offers</h2>
|
<h2 class="text-2xl font-bold mb-4">My Trade Offers</h2>
|
||||||
|
|
@ -96,9 +95,5 @@
|
||||||
<p>No trade offers found.</p>
|
<p>No trade offers found.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
<a href="{% url 'trade_offer_create' %}" class="btn btn-success">Create New Offer</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<!-- Offer Details Card -->
|
<!-- Offer Details Card -->
|
||||||
<div class="card bg-base-100 shadow-lg">
|
<div class="card bg-base-100 shadow-lg">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="text-gray-700">
|
<p>
|
||||||
<strong>Created At:</strong> {{ object.created_at|date:"M d, Y H:i" }}<br>
|
<strong>Created At:</strong> {{ object.created_at|date:"M d, Y H:i" }}<br>
|
||||||
<strong>Updated At:</strong> {{ object.updated_at|date:"M d, Y H:i" }}<br>
|
<strong>Updated At:</strong> {{ object.updated_at|date:"M d, Y H:i" }}<br>
|
||||||
{% if object.initiated_by.user == request.user or object.accepted_by and object.accepted_by.user == request.user %}
|
{% if object.initiated_by.user == request.user or object.accepted_by and object.accepted_by.user == request.user %}
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
{% for card in object.want_cards.all %}
|
{% for card in object.want_cards.all %}
|
||||||
{{ card.name }}{% if not forloop.last %}, {% endif %}
|
{{ card.name }}{% if not forloop.last %}, {% endif %}
|
||||||
{% endfor %}<br>
|
{% endfor %}<br>
|
||||||
<strong>Status:</strong> {% if object.is_active %}Open{% else %}Closed{% endif %}
|
<strong>Status:</strong> {% if object.is_closed %}Closed{% else %}Open{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,12 @@
|
||||||
<option value="" disabled>{{ placeholder }}</option>
|
<option value="" disabled>{{ placeholder }}</option>
|
||||||
{% for card in available_cards %}
|
{% for card in available_cards %}
|
||||||
<option
|
<option
|
||||||
value="{{ card.pk }}"
|
value="{{ card.pk }}:{{ card.selected_quantity }}"
|
||||||
data-quantity="{% if card.pk|stringformat:"s" in selected_values %}{{ card.selected_quantity }}{% else %}1{% endif %}"
|
|
||||||
{% if card.pk|stringformat:"s" in selected_values %}selected{% endif %}
|
{% if card.pk|stringformat:"s" in selected_values %}selected{% endif %}
|
||||||
data-html-content='{{ card|card_badge_inline:"__QUANTITY__" }}'
|
data-html-content='{{ card|card_badge_inline:card.selected_quantity }}'
|
||||||
data-name="{{ card.name }}"
|
data-name="{{ card.name }}"
|
||||||
data-rarity="{{ card.rarity.icons }}"
|
data-rarity="{{ card.rarity.icons }}"
|
||||||
data-cardset="{{ card.cardset.name }}"
|
data-cardset="{{ card.cardset.name }}">
|
||||||
data-style="{{ card.style }}">
|
|
||||||
{{ card.name }} {{ card.rarity.icons }} {{ card.cardset.name }}
|
{{ card.name }} {{ card.rarity.icons }} {{ card.cardset.name }}
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
@ -24,60 +22,61 @@
|
||||||
<script defer>
|
<script defer>
|
||||||
if (!window.updateGlobalCardFilters) {
|
if (!window.updateGlobalCardFilters) {
|
||||||
window.updateGlobalCardFilters = function() {
|
window.updateGlobalCardFilters = function() {
|
||||||
|
console.log("updateGlobalCardFilters called.");
|
||||||
const selects = document.querySelectorAll('.card-multiselect');
|
const selects = document.querySelectorAll('.card-multiselect');
|
||||||
|
|
||||||
// Gather all selected card IDs using the Choices.js API.
|
// Rebuilt every call to updateGlobalCardFilters.
|
||||||
const globalSelectedIds = [];
|
const globalSelectedIds = [];
|
||||||
let globalRarity = null;
|
let globalRarity = null;
|
||||||
selects.forEach(select => {
|
|
||||||
const choicesInstance = select.choicesInstance;
|
|
||||||
const selectedValues = choicesInstance ? choicesInstance.getValue(true) : [];
|
|
||||||
|
|
||||||
// Build a list of unique selected card IDs.
|
selects.forEach(select => {
|
||||||
selectedValues.forEach(val => {
|
const selectedValuesRaw = select.choicesInstance ? select.choicesInstance.getValue(true) : [];
|
||||||
if (val && globalSelectedIds.indexOf(val) === -1) {
|
const selectedValues = selectedValuesRaw.map(val => val.split(':')[0]);
|
||||||
globalSelectedIds.push(val);
|
|
||||||
|
selectedValues.forEach(cardId => {
|
||||||
|
if (cardId && globalSelectedIds.indexOf(cardId) === -1) {
|
||||||
|
globalSelectedIds.push(cardId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set the global rarity based on the first select that has a selection.
|
|
||||||
if (selectedValues.length > 0 && globalRarity === null) {
|
if (selectedValues.length > 0 && globalRarity === null) {
|
||||||
const option = select.querySelector(`option[value="${selectedValues[0]}"]`);
|
const option = select.querySelector(`option[value^="${selectedValues[0]}:"]`);
|
||||||
if (option) {
|
if (option) {
|
||||||
globalRarity = option.getAttribute('data-rarity');
|
globalRarity = option.getAttribute('data-rarity');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update each option element in every select.
|
console.log("Global selected card IDs:", globalSelectedIds, "Current Global rarity:", globalRarity);
|
||||||
|
|
||||||
selects.forEach(select => {
|
selects.forEach(select => {
|
||||||
|
if (select.choicesInstance && select.choicesInstance.dropdown.element) {
|
||||||
|
// reset all options to enabled.
|
||||||
select.querySelectorAll('option').forEach(function(option) {
|
select.querySelectorAll('option').forEach(function(option) {
|
||||||
const cardId = option.value;
|
option.disabled = false;
|
||||||
const optionRarity = option.getAttribute('data-rarity');
|
|
||||||
// Determine if the card is selected using the Choices.js API.
|
|
||||||
const isSelected = select.choicesInstance ? select.choicesInstance.getValue(true).includes(cardId) : option.selected;
|
|
||||||
|
|
||||||
// If no cards are selected globally, globalRarity is null, so all options pass the rarity filter.
|
|
||||||
const passesRarity = (!globalRarity) || isSelected || (optionRarity === globalRarity);
|
|
||||||
// Ensure that if a card is selected in any multiselect, it remains unique.
|
|
||||||
const passesUnique = isSelected || !globalSelectedIds.includes(cardId);
|
|
||||||
|
|
||||||
option.disabled = !(passesRarity && passesUnique);
|
|
||||||
});
|
});
|
||||||
|
// reset all items to visible.
|
||||||
|
select.choicesInstance.dropdown.element.querySelectorAll('[data-card-id]').forEach(function(item) {
|
||||||
|
item.style.display = '';
|
||||||
|
});
|
||||||
|
// filter out options and items that don't match the global rarity.
|
||||||
|
debugger;
|
||||||
|
if (globalRarity) {
|
||||||
|
select.querySelectorAll('option[data-rarity]:not([data-rarity="'+globalRarity+'"])').forEach(function(option) {
|
||||||
|
option.disabled = true;
|
||||||
|
});
|
||||||
|
select.choicesInstance.dropdown.element.querySelectorAll('[data-rarity]:not([data-rarity="'+globalRarity+'"])').forEach(function(item) {
|
||||||
|
item.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Update the display for the Choices.js dropdown.
|
// filter out options and items that match the global selected card IDs.
|
||||||
if (select.choicesInstance) {
|
for (const cardId of globalSelectedIds) {
|
||||||
const dropdown = select.choicesInstance.dropdown.element;
|
select.choicesInstance.dropdown.element.querySelectorAll('[data-card-id="' + cardId + '"]').forEach(function(item) {
|
||||||
if (dropdown) {
|
item.style.display = 'none';
|
||||||
dropdown.querySelectorAll('[data-choice]').forEach(function(item) {
|
});
|
||||||
const cardId = item.getAttribute('data-value');
|
select.querySelectorAll('option[data-card-id="'+cardId+'"]:not(option[selected])').forEach(function(option) {
|
||||||
const itemRarity = item.getAttribute('data-rarity');
|
option.disabled = true;
|
||||||
const isSelected = select.choicesInstance.getValue(true).includes(cardId);
|
|
||||||
|
|
||||||
const passesRarity = (!globalRarity) || isSelected || (itemRarity === globalRarity);
|
|
||||||
const passesUnique = isSelected || !globalSelectedIds.includes(cardId);
|
|
||||||
|
|
||||||
item.style.display = (passesRarity && passesUnique) ? '' : 'none';
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,43 +85,36 @@ if (!window.updateGlobalCardFilters) {
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log("DOM fully loaded. Initializing card multiselect for field '{{ field_id }}'");
|
||||||
const selectField = document.getElementById('{{ field_id }}');
|
const selectField = document.getElementById('{{ field_id }}');
|
||||||
const placeholder = selectField.getAttribute('data-placeholder') || '';
|
const placeholder = selectField.getAttribute('data-placeholder') || '';
|
||||||
|
|
||||||
// Remove the name attribute from the select field since we don't use the select itself but build our own hidden inputs.
|
|
||||||
if (selectField.hasAttribute('name')) {
|
|
||||||
let originalName = selectField.getAttribute('name');
|
|
||||||
selectField.setAttribute('data-field-name', originalName);
|
|
||||||
selectField.removeAttribute('name');
|
|
||||||
}
|
|
||||||
|
|
||||||
const choicesInstance = new Choices(selectField, {
|
const choicesInstance = new Choices(selectField, {
|
||||||
removeItemButton: false,
|
removeItemButton: false,
|
||||||
placeholderValue: placeholder,
|
placeholderValue: placeholder,
|
||||||
searchEnabled: true,
|
searchEnabled: true,
|
||||||
shouldSort: false,
|
shouldSort: false,
|
||||||
allowHTML: true,
|
allowHTML: true,
|
||||||
|
closeDropdownOnSelect: false,
|
||||||
|
removeItemButton: true,
|
||||||
|
searchFields: ['data-name'],
|
||||||
|
resetScrollPosition: false,
|
||||||
callbackOnCreateTemplates: function(template) {
|
callbackOnCreateTemplates: function(template) {
|
||||||
// Helper to get HTML content and do token replacement if necessary.
|
// Helper to get HTML content and replace the __QUANTITY__ token.
|
||||||
const getCardContent = (data) => {
|
const getCardContent = (data) => {
|
||||||
let htmlContent = (data.element && data.element.getAttribute('data-html-content')) || data.label;
|
let htmlContent = (data.element && data.element.getAttribute('data-html-content')) || data.label;
|
||||||
let quantity = data.element ? data.element.getAttribute('data-quantity') : "1";
|
|
||||||
// Replace placeholder token (__QUANTITY__) with the current quantity.
|
|
||||||
if (htmlContent.includes('__QUANTITY__')) {
|
|
||||||
htmlContent = htmlContent.replace(/__QUANTITY__/g, quantity);
|
|
||||||
}
|
|
||||||
return htmlContent;
|
return htmlContent;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCard = (classNames, data, type) => {
|
const renderCard = (classNames, data, type) => {
|
||||||
const rarity = data.element ? data.element.getAttribute('data-rarity') : '';
|
const rarity = data.element ? data.element.getAttribute('data-rarity') : '';
|
||||||
|
const cardId = data.element ? data.element.getAttribute('value').split(':')[0] : 0;
|
||||||
const content = getCardContent(data);
|
const content = getCardContent(data);
|
||||||
if (type === 'item') {
|
if (type === 'item') {
|
||||||
return template(`
|
return template(`
|
||||||
<div class="${classNames.item} mx-auto w-max ${data.highlighted ? classNames.highlightedState : ''} relative"
|
<div class="${classNames.item} mx-auto w-max ${data.highlighted ? classNames.highlightedState : ''} relative"
|
||||||
data-id="${data.id}"
|
data-id="${data.id}"
|
||||||
data-value="${data.value}"
|
data-card-id="${cardId}"
|
||||||
data-quantity="${data.element ? data.element.getAttribute('data-quantity') : 1}"
|
|
||||||
data-item
|
data-item
|
||||||
data-rarity="${rarity}"
|
data-rarity="${rarity}"
|
||||||
aria-selected="true"
|
aria-selected="true"
|
||||||
|
|
@ -141,7 +133,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<div class="${classNames.item} ${extraClasses} ${data.highlighted ? classNames.highlightedState : ''} mx-auto w-max"
|
<div class="${classNames.item} ${extraClasses} ${data.highlighted ? classNames.highlightedState : ''} mx-auto w-max"
|
||||||
${extraAttributes}
|
${extraAttributes}
|
||||||
data-id="${data.id}"
|
data-id="${data.id}"
|
||||||
data-value="${data.value}"
|
data-card-id="${cardId}"
|
||||||
|
data-choice
|
||||||
data-rarity="${rarity}"
|
data-rarity="${rarity}"
|
||||||
style="cursor: pointer;">
|
style="cursor: pointer;">
|
||||||
${content}
|
${content}
|
||||||
|
|
@ -161,7 +154,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Associate the Choices instance with the select.
|
console.log("Initialized Choices.js instance for field '{{ field_id }}':", choicesInstance);
|
||||||
|
// Associate the Choices instance with the select field.
|
||||||
selectField.choicesInstance = choicesInstance;
|
selectField.choicesInstance = choicesInstance;
|
||||||
|
|
||||||
if (!window.cardMultiselectInstances) {
|
if (!window.cardMultiselectInstances) {
|
||||||
|
|
@ -170,122 +164,95 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
window.cardMultiselectInstances.push(selectField);
|
window.cardMultiselectInstances.push(selectField);
|
||||||
|
|
||||||
selectField.addEventListener('change', function() {
|
selectField.addEventListener('change', function() {
|
||||||
|
console.log("Select field changed. Current selected values:", selectField.choicesInstance.getValue(true));
|
||||||
if (window.updateGlobalCardFilters) {
|
if (window.updateGlobalCardFilters) {
|
||||||
window.updateGlobalCardFilters();
|
window.updateGlobalCardFilters();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Scope the event listener to the closest .choices container.
|
// Listen for increment/decrement clicks (scoped to the choices container).
|
||||||
const choicesContainer = selectField.closest('.choices') || document;
|
const choicesContainer = selectField.closest('.choices') || document;
|
||||||
|
|
||||||
choicesContainer.addEventListener('click', function(e) {
|
choicesContainer.addEventListener('click', function(e) {
|
||||||
if (e.target.classList.contains('increment')) {
|
if (e.target.classList.contains('increment')) {
|
||||||
|
debugger;
|
||||||
|
console.log("Increment button clicked.");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
const container = e.target.closest('[data-item]');
|
const container = e.target.closest('[data-item]');
|
||||||
if (container) {
|
if (container) {
|
||||||
let quantityBadge = container.querySelector('.card-quantity-badge');
|
let quantityBadge = container.querySelector('.card-quantity-badge');
|
||||||
if (!quantityBadge) {
|
let quantity = getOptionQuantity(container);
|
||||||
quantityBadge = document.createElement('span');
|
console.log("Increment action on card", container.getAttribute('data-card-id'), "original quantity:", quantity, "new quantity:", quantity + 1);
|
||||||
quantityBadge.className = 'card-quantity-badge';
|
|
||||||
quantityBadge.style.marginRight = '0.5rem';
|
|
||||||
container.insertBefore(quantityBadge, container.firstChild);
|
|
||||||
}
|
|
||||||
let quantity = parseInt(container.getAttribute('data-quantity')) || 1;
|
|
||||||
quantity = quantity + 1;
|
quantity = quantity + 1;
|
||||||
quantityBadge.innerText = quantity;
|
quantityBadge.innerText = quantity;
|
||||||
updateOptionQuantity(container, quantity);
|
updateOptionQuantity(container, quantity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.target.classList.contains('decrement')) {
|
if (e.target.classList.contains('decrement')) {
|
||||||
|
debugger;
|
||||||
|
console.log("Decrement button clicked.");
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
const container = e.target.closest('[data-item]');
|
const container = e.target.closest('[data-item]');
|
||||||
if (container) {
|
if (container) {
|
||||||
let quantityBadge = container.querySelector('.card-quantity-badge');
|
let quantityBadge = container.querySelector('.card-quantity-badge');
|
||||||
if (!quantityBadge) {
|
let quantity = getOptionQuantity(container);
|
||||||
quantityBadge = document.createElement('span');
|
const cardId = container.getAttribute('data-card-id');
|
||||||
quantityBadge.className = 'card-quantity-badge';
|
if (quantity == 1) {
|
||||||
quantityBadge.style.marginRight = '0.5rem';
|
console.log("Decrement action: quantity is 1 for card", cardId, "initiating removal.");
|
||||||
container.insertBefore(quantityBadge, container.firstChild);
|
const option = selectField.querySelector('option[value^="' + cardId + ':"]');
|
||||||
}
|
|
||||||
let quantity = parseInt(container.getAttribute('data-quantity')) || 1;
|
|
||||||
if (quantity === 1) {
|
|
||||||
const cardId = container.getAttribute('data-value');
|
|
||||||
let removed = false;
|
|
||||||
const activeValues = choicesInstance.getValue(true);
|
|
||||||
for (let val of activeValues) {
|
|
||||||
if (val === cardId) {
|
|
||||||
choicesInstance.removeActiveItemsByValue(val);
|
|
||||||
removed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!removed) {
|
|
||||||
container.remove();
|
|
||||||
const option = selectField.querySelector('option[value="' + cardId + '"]');
|
|
||||||
if (option) {
|
if (option) {
|
||||||
|
console.log("Removing card from Choices.js instance. Value removed:", option.value);
|
||||||
|
choicesInstance.removeActiveItemsByValue(option.value);
|
||||||
option.selected = false;
|
option.selected = false;
|
||||||
option.setAttribute('data-quantity', '1');
|
option.value = cardId + ':1';
|
||||||
}
|
} else {
|
||||||
|
console.log("No active item found for card", cardId);
|
||||||
}
|
}
|
||||||
if (window.updateGlobalCardFilters) {
|
if (window.updateGlobalCardFilters) {
|
||||||
window.updateGlobalCardFilters();
|
window.updateGlobalCardFilters();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.log("Decrement action on card", cardId, "reducing quantity from", quantity, "to", quantity - 1);
|
||||||
quantity = quantity - 1;
|
quantity = quantity - 1;
|
||||||
quantityBadge.innerText = quantity;
|
quantityBadge.innerText = quantity;
|
||||||
updateOptionQuantity(container, quantity);
|
updateOptionQuantity(container, quantity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.target.closest('[data-item]') && !e.target.classList.contains('increment') && !e.target.classList.contains('decrement')) {
|
if (e.target.closest('[data-item]') &&
|
||||||
|
!e.target.classList.contains('increment') &&
|
||||||
|
!e.target.classList.contains('decrement')) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper: update only the data-quantity in both the UI container and its corresponding select option.
|
// Update the option's value by rewriting the "card:qty" string.
|
||||||
function updateOptionQuantity(container, quantity) {
|
function updateOptionQuantity(item, quantity) {
|
||||||
container.setAttribute('data-quantity', quantity);
|
debugger;
|
||||||
const cardId = container.getAttribute('data-value') || container.getAttribute('data-id');
|
const cardId = item.getAttribute('data-card-id');
|
||||||
const option = selectField.querySelector('option[value="' + cardId + '"]');
|
console.log("Updating option quantity for card", cardId, "to", quantity);
|
||||||
|
const option = item.closest('.choices__inner').querySelector('option[value^="' + cardId + ':"]');
|
||||||
if (option) {
|
if (option) {
|
||||||
option.setAttribute('data-quantity', quantity);
|
option.value = cardId + ':' + quantity;
|
||||||
|
console.log("Updated option value for card", cardId, ":", option.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New Form Submission Handler: Before the form submits, create hidden inputs with "cardID:quantity".
|
function getOptionQuantity(item) {
|
||||||
const formElement = selectField.closest('form');
|
debugger;
|
||||||
if (formElement) {
|
const cardId = item.getAttribute('data-card-id');
|
||||||
formElement.addEventListener('submit', function(e) {
|
const option = item.closest('.choices__inner').querySelector('option[value^="' + cardId + ':"]');
|
||||||
// Remove previously generated hidden inputs to avoid duplications
|
if (option) {
|
||||||
const existingHiddenInputs = formElement.querySelectorAll('input[data-generated="multiselect"]');
|
return parseInt(option.value.split(':')[1]);
|
||||||
existingHiddenInputs.forEach(input => input.remove());
|
}
|
||||||
|
|
||||||
const multiselects = formElement.querySelectorAll('.card-multiselect');
|
|
||||||
multiselects.forEach((select) => {
|
|
||||||
// Use the stored field name from the data-field-name attribute.
|
|
||||||
const fieldName = select.getAttribute('data-field-name');
|
|
||||||
|
|
||||||
Array.from(select.selectedOptions).forEach((option) => {
|
|
||||||
const cardId = option.value;
|
|
||||||
const quantity = option.getAttribute('data-quantity') || '1';
|
|
||||||
const hiddenInput = document.createElement('input');
|
|
||||||
hiddenInput.type = 'hidden';
|
|
||||||
hiddenInput.name = fieldName;
|
|
||||||
hiddenInput.value = cardId + ':' + quantity;
|
|
||||||
// Mark this element as generated by our multiselect handler.
|
|
||||||
hiddenInput.setAttribute('data-generated', 'multiselect');
|
|
||||||
formElement.appendChild(hiddenInput);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial filters update on page load.
|
// Initial global filters update on page load.
|
||||||
if (window.updateGlobalCardFilters) {
|
if (window.updateGlobalCardFilters) {
|
||||||
window.updateGlobalCardFilters();
|
window.updateGlobalCardFilters();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ if (!window.tradeOfferCard) {
|
||||||
The outer div now only establishes Alpine's data context.
|
The outer div now only establishes Alpine's data context.
|
||||||
The dynamic height adjustment (x-init & x-effect with x-ref) has been removed.
|
The dynamic height adjustment (x-init & x-effect with x-ref) has been removed.
|
||||||
-->
|
-->
|
||||||
<div x-data="tradeOfferCard()" class="transition-all duration-700 trade-offer-card"
|
<div x-data="tradeOfferCard()" class="transition-all duration-500 trade-offer-card"
|
||||||
@toggle-all.window="setBadge($event.detail.expanded)">
|
@toggle-all.window="setBadge($event.detail.expanded)">
|
||||||
|
|
||||||
<!-- Flip container providing perspective -->
|
<!-- Flip container providing perspective -->
|
||||||
|
|
@ -40,6 +40,7 @@ if (!window.tradeOfferCard) {
|
||||||
<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">
|
<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="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">
|
||||||
|
|
@ -57,9 +58,11 @@ if (!window.tradeOfferCard) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Main Trade Offer Row -->
|
<!-- 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="grid grid-cols-2 gap-2 items-center">
|
<div class="grid grid-cols-2 gap-2 items-center">
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center">
|
||||||
|
|
@ -94,6 +97,7 @@ if (!window.tradeOfferCard) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
{% if have_cards_available|length > 1 or want_cards_available|length > 1 %}
|
{% if have_cards_available|length > 1 or want_cards_available|length > 1 %}
|
||||||
<div class="flex justify-center my-1">
|
<div class="flex justify-center my-1">
|
||||||
<svg @click="badgeExpanded = !badgeExpanded"
|
<svg @click="badgeExpanded = !badgeExpanded"
|
||||||
|
|
@ -210,6 +214,7 @@ if (!window.tradeOfferCard) {
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if offer.acceptances.all|length > 1 %}
|
||||||
<div class="flex justify-center my-1">
|
<div class="flex justify-center my-1">
|
||||||
<svg @click="acceptanceExpanded = !acceptanceExpanded"
|
<svg @click="acceptanceExpanded = !acceptanceExpanded"
|
||||||
x-bind:class="{ 'rotate-180': acceptanceExpanded }"
|
x-bind:class="{ 'rotate-180': acceptanceExpanded }"
|
||||||
|
|
@ -219,6 +224,7 @@ if (!window.tradeOfferCard) {
|
||||||
d="M19 9l-7 7-7-7" />
|
d="M19 9l-7 7-7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between px-2 pb-2 self-end">
|
<div class="flex justify-between px-2 pb-2 self-end">
|
||||||
<!-- Back-to-front flip button -->
|
<!-- Back-to-front flip button -->
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import TradeOffer
|
from .models import TradeOffer, TradeOfferHaveCard, TradeOfferWantCard, TradeAcceptance
|
||||||
|
|
||||||
admin.site.register(TradeOffer)
|
admin.site.register(TradeOffer)
|
||||||
|
admin.site.register(TradeOfferHaveCard)
|
||||||
|
admin.site.register(TradeOfferWantCard)
|
||||||
|
admin.site.register(TradeAcceptance)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 5.1.2 on 2025-03-09 05:08
|
# Generated by Django 5.1.2 on 2025-03-13 01:46
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
@ -18,14 +18,10 @@ class Migration(migrations.Migration):
|
||||||
name='TradeOffer',
|
name='TradeOffer',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||||
('manually_closed', models.BooleanField(db_index=True, default=False)),
|
('is_closed', models.BooleanField(db_index=True, default=False)),
|
||||||
('hash', models.CharField(editable=False, max_length=9)),
|
('hash', models.CharField(editable=False, max_length=9)),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
('total_have_quantity', models.PositiveIntegerField(default=0, editable=False)),
|
|
||||||
('total_want_quantity', models.PositiveIntegerField(default=0, editable=False)),
|
|
||||||
('total_have_accepted', models.PositiveIntegerField(default=0, editable=False)),
|
|
||||||
('total_want_accepted', models.PositiveIntegerField(default=0, editable=False)),
|
|
||||||
('initiated_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='initiated_trade_offers', to='accounts.friendcode')),
|
('initiated_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='initiated_trade_offers', to='accounts.friendcode')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -33,7 +29,7 @@ class Migration(migrations.Migration):
|
||||||
name='TradeAcceptance',
|
name='TradeAcceptance',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('state', models.CharField(choices=[('ACCEPTED', 'Accepted'), ('SENT', 'Sent'), ('RECEIVED', 'Received'), ('THANKED_BY_INITIATOR', 'Thanked by Initiator'), ('THANKED_BY_ACCEPTOR', 'Thanked by Acceptor'), ('THANKED_BY_BOTH', 'Thanked by Both'), ('REJECTED_BY_INITIATOR', 'Rejected by Initiator'), ('REJECTED_BY_ACCEPTOR', 'Rejected by Acceptor')], db_index=True, default='ACCEPTED', max_length=25)),
|
('state', models.CharField(choices=[('ACCEPTED', 'Accepted'), ('SENT', 'Sent'), ('RECEIVED', 'Received'), ('THANKED_BY_INITIATOR', 'Thanked by Initiator'), ('THANKED_BY_ACCEPTOR', 'Thanked by Acceptor'), ('THANKED_BY_BOTH', 'Thanked by Both'), ('REJECTED_BY_INITIATOR', 'Rejected by Initiator'), ('REJECTED_BY_ACCEPTOR', 'Rejected by Acceptor')], default='ACCEPTED', max_length=25)),
|
||||||
('hash', models.CharField(blank=True, editable=False, max_length=9)),
|
('hash', models.CharField(blank=True, editable=False, max_length=9)),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
|
@ -48,6 +44,7 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('quantity', models.PositiveIntegerField(default=1)),
|
('quantity', models.PositiveIntegerField(default=1)),
|
||||||
|
('qty_accepted', models.PositiveIntegerField(default=0, editable=False)),
|
||||||
('card', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cards.card')),
|
('card', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cards.card')),
|
||||||
('trade_offer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trade_offer_have_cards', to='trades.tradeoffer')),
|
('trade_offer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trade_offer_have_cards', to='trades.tradeoffer')),
|
||||||
],
|
],
|
||||||
|
|
@ -65,6 +62,7 @@ class Migration(migrations.Migration):
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('quantity', models.PositiveIntegerField(default=1)),
|
('quantity', models.PositiveIntegerField(default=1)),
|
||||||
|
('qty_accepted', models.PositiveIntegerField(default=0, editable=False)),
|
||||||
('card', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cards.card')),
|
('card', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cards.card')),
|
||||||
('trade_offer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trade_offer_want_cards', to='trades.tradeoffer')),
|
('trade_offer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trade_offer_want_cards', to='trades.tradeoffer')),
|
||||||
],
|
],
|
||||||
|
|
@ -77,8 +75,4 @@ class Migration(migrations.Migration):
|
||||||
name='want_cards',
|
name='want_cards',
|
||||||
field=models.ManyToManyField(related_name='trade_offers_want', through='trades.TradeOfferWantCard', to='cards.card'),
|
field=models.ManyToManyField(related_name='trade_offers_want', through='trades.TradeOfferWantCard', to='cards.card'),
|
||||||
),
|
),
|
||||||
migrations.AddIndex(
|
|
||||||
model_name='tradeoffer',
|
|
||||||
index=models.Index(fields=['manually_closed'], name='trades_trad_manuall_b3b74c_idx'),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
182
trades/models.py
182
trades/models.py
|
|
@ -7,7 +7,7 @@ from accounts.models import FriendCode
|
||||||
|
|
||||||
class TradeOffer(models.Model):
|
class TradeOffer(models.Model):
|
||||||
id = models.AutoField(primary_key=True)
|
id = models.AutoField(primary_key=True)
|
||||||
manually_closed = models.BooleanField(default=False, db_index=True)
|
is_closed = models.BooleanField(default=False, db_index=True)
|
||||||
hash = models.CharField(max_length=9, editable=False)
|
hash = models.CharField(max_length=9, editable=False)
|
||||||
initiated_by = models.ForeignKey(
|
initiated_by = models.ForeignKey(
|
||||||
"accounts.FriendCode",
|
"accounts.FriendCode",
|
||||||
|
|
@ -28,12 +28,6 @@ class TradeOffer(models.Model):
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
# New denormalized fields for aggregated counts
|
|
||||||
total_have_quantity = models.PositiveIntegerField(default=0, editable=False)
|
|
||||||
total_want_quantity = models.PositiveIntegerField(default=0, editable=False)
|
|
||||||
total_have_accepted = models.PositiveIntegerField(default=0, editable=False)
|
|
||||||
total_want_accepted = models.PositiveIntegerField(default=0, editable=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
want_names = ", ".join([x.name for x in self.want_cards.all()])
|
want_names = ", ".join([x.name for x in self.want_cards.all()])
|
||||||
have_names = ", ".join([x.name for x in self.have_cards.all()])
|
have_names = ", ".join([x.name for x in self.have_cards.all()])
|
||||||
|
|
@ -46,66 +40,6 @@ class TradeOffer(models.Model):
|
||||||
self.hash = hashlib.md5((str(self.id) + "z").encode("utf-8")).hexdigest()[:8] + "z"
|
self.hash = hashlib.md5((str(self.id) + "z").encode("utf-8")).hexdigest()[:8] + "z"
|
||||||
super().save(update_fields=["hash"])
|
super().save(update_fields=["hash"])
|
||||||
|
|
||||||
def update_aggregates(self):
|
|
||||||
"""
|
|
||||||
Recalculate and update aggregated fields from related have/want cards and acceptances.
|
|
||||||
"""
|
|
||||||
from django.db.models import Sum
|
|
||||||
from trades.models import TradeAcceptance
|
|
||||||
|
|
||||||
# Calculate total quantities from through models
|
|
||||||
have_agg = self.trade_offer_have_cards.aggregate(total=Sum("quantity"))
|
|
||||||
want_agg = self.trade_offer_want_cards.aggregate(total=Sum("quantity"))
|
|
||||||
self.total_have_quantity = have_agg["total"] or 0
|
|
||||||
self.total_want_quantity = want_agg["total"] or 0
|
|
||||||
|
|
||||||
# Define acceptance states that count as active.
|
|
||||||
active_states = [
|
|
||||||
TradeAcceptance.AcceptanceState.ACCEPTED,
|
|
||||||
TradeAcceptance.AcceptanceState.SENT,
|
|
||||||
TradeAcceptance.AcceptanceState.RECEIVED,
|
|
||||||
TradeAcceptance.AcceptanceState.THANKED_BY_INITIATOR,
|
|
||||||
TradeAcceptance.AcceptanceState.THANKED_BY_ACCEPTOR,
|
|
||||||
TradeAcceptance.AcceptanceState.THANKED_BY_BOTH,
|
|
||||||
]
|
|
||||||
|
|
||||||
# Compute accepted counts based on matching card IDs.
|
|
||||||
have_card_ids = list(self.trade_offer_have_cards.values_list("card_id", flat=True))
|
|
||||||
want_card_ids = list(self.trade_offer_want_cards.values_list("card_id", flat=True))
|
|
||||||
|
|
||||||
self.total_have_accepted = TradeAcceptance.objects.filter(
|
|
||||||
trade_offer=self,
|
|
||||||
state__in=active_states,
|
|
||||||
requested_card_id__in=have_card_ids,
|
|
||||||
).count()
|
|
||||||
|
|
||||||
self.total_want_accepted = TradeAcceptance.objects.filter(
|
|
||||||
trade_offer=self,
|
|
||||||
state__in=active_states,
|
|
||||||
offered_card_id__in=want_card_ids,
|
|
||||||
).count()
|
|
||||||
|
|
||||||
# Save updated aggregate values so they are denormalized in the database.
|
|
||||||
self.save(update_fields=[
|
|
||||||
"total_have_quantity",
|
|
||||||
"total_want_quantity",
|
|
||||||
"total_have_accepted",
|
|
||||||
"total_want_accepted",
|
|
||||||
])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_closed(self):
|
|
||||||
if self.manually_closed:
|
|
||||||
return True
|
|
||||||
# Utilize denormalized fields for faster check.
|
|
||||||
return not (self.total_have_accepted < self.total_have_quantity and
|
|
||||||
self.total_want_accepted < self.total_want_quantity)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
models.Index(fields=['manually_closed']),
|
|
||||||
]
|
|
||||||
|
|
||||||
class TradeOfferHaveCard(models.Model):
|
class TradeOfferHaveCard(models.Model):
|
||||||
"""
|
"""
|
||||||
Through model for TradeOffer.have_cards.
|
Through model for TradeOffer.have_cards.
|
||||||
|
|
@ -119,9 +53,11 @@ class TradeOfferHaveCard(models.Model):
|
||||||
)
|
)
|
||||||
card = models.ForeignKey("cards.Card", on_delete=models.PROTECT, db_index=True)
|
card = models.ForeignKey("cards.Card", on_delete=models.PROTECT, db_index=True)
|
||||||
quantity = models.PositiveIntegerField(default=1)
|
quantity = models.PositiveIntegerField(default=1)
|
||||||
|
# New field to track number of accepted cards for this entry.
|
||||||
|
qty_accepted = models.PositiveIntegerField(default=0, editable=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.card.name} x{self.quantity}"
|
return f"{self.card.name} x{self.quantity} (Accepted: {self.qty_accepted})"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("trade_offer", "card")
|
unique_together = ("trade_offer", "card")
|
||||||
|
|
@ -138,9 +74,11 @@ class TradeOfferWantCard(models.Model):
|
||||||
)
|
)
|
||||||
card = models.ForeignKey("cards.Card", on_delete=models.PROTECT)
|
card = models.ForeignKey("cards.Card", on_delete=models.PROTECT)
|
||||||
quantity = models.PositiveIntegerField(default=1)
|
quantity = models.PositiveIntegerField(default=1)
|
||||||
|
# New field for tracking accepted count.
|
||||||
|
qty_accepted = models.PositiveIntegerField(default=0, editable=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.card.name} x{self.quantity}"
|
return f"{self.card.name} x{self.quantity} (Accepted: {self.qty_accepted})"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("trade_offer", "card")
|
unique_together = ("trade_offer", "card")
|
||||||
|
|
@ -171,73 +109,25 @@ class TradeAcceptance(models.Model):
|
||||||
requested_card = models.ForeignKey(
|
requested_card = models.ForeignKey(
|
||||||
"cards.Card",
|
"cards.Card",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='accepted_requested',
|
related_name='accepted_requested'
|
||||||
db_index=True
|
|
||||||
)
|
)
|
||||||
# And one card from the initiator's wanted cards (from want_cards)
|
# And one card from the initiator's wanted cards (from want_cards)
|
||||||
offered_card = models.ForeignKey(
|
offered_card = models.ForeignKey(
|
||||||
"cards.Card",
|
"cards.Card",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='accepted_offered',
|
related_name='accepted_offered'
|
||||||
db_index=True
|
|
||||||
)
|
)
|
||||||
state = models.CharField(
|
state = models.CharField(
|
||||||
max_length=25,
|
max_length=25,
|
||||||
choices=AcceptanceState.choices,
|
choices=AcceptanceState.choices,
|
||||||
default=AcceptanceState.ACCEPTED,
|
default=AcceptanceState.ACCEPTED
|
||||||
db_index=True
|
|
||||||
)
|
)
|
||||||
hash = models.CharField(max_length=9, editable=False, blank=True)
|
hash = models.CharField(max_length=9, editable=False, blank=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def mark_thanked(self, friend_code):
|
|
||||||
"""
|
|
||||||
Mark this acceptance as "thanked" by the given friend_code.
|
|
||||||
Allowed transitions:
|
|
||||||
- If the current state is RECEIVED:
|
|
||||||
* If the initiator thanks, transition to THANKED_BY_INITIATOR.
|
|
||||||
* If the acceptor thanks, transition to THANKED_BY_ACCEPTOR.
|
|
||||||
- If already partially thanked:
|
|
||||||
* If state is THANKED_BY_INITIATOR and the acceptor thanks, transition to THANKED_BY_BOTH.
|
|
||||||
* If state is THANKED_BY_ACCEPTOR and the initiator thanks, transition to THANKED_BY_BOTH.
|
|
||||||
Only parties involved in the trade (either the initiator or the acceptor) can mark it as thanked.
|
|
||||||
"""
|
|
||||||
if self.state not in [self.AcceptanceState.RECEIVED,
|
|
||||||
self.AcceptanceState.THANKED_BY_INITIATOR,
|
|
||||||
self.AcceptanceState.THANKED_BY_ACCEPTOR]:
|
|
||||||
raise ValidationError("Cannot mark thanked in the current state.")
|
|
||||||
|
|
||||||
if friend_code == self.trade_offer.initiated_by:
|
|
||||||
# Initiator is marking thanks.
|
|
||||||
if self.state == self.AcceptanceState.RECEIVED:
|
|
||||||
self.state = self.AcceptanceState.THANKED_BY_INITIATOR
|
|
||||||
elif self.state == self.AcceptanceState.THANKED_BY_ACCEPTOR:
|
|
||||||
self.state = self.AcceptanceState.THANKED_BY_BOTH
|
|
||||||
elif self.state == self.AcceptanceState.THANKED_BY_INITIATOR:
|
|
||||||
# Already thanked by the initiator.
|
|
||||||
return
|
|
||||||
elif friend_code == self.accepted_by:
|
|
||||||
# Acceptor is marking thanks.
|
|
||||||
if self.state == self.AcceptanceState.RECEIVED:
|
|
||||||
self.state = self.AcceptanceState.THANKED_BY_ACCEPTOR
|
|
||||||
elif self.state == self.AcceptanceState.THANKED_BY_INITIATOR:
|
|
||||||
self.state = self.AcceptanceState.THANKED_BY_BOTH
|
|
||||||
elif self.state == self.AcceptanceState.THANKED_BY_ACCEPTOR:
|
|
||||||
# Already thanked by the acceptor.
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
raise PermissionDenied("You are not a party to this trade acceptance.")
|
|
||||||
|
|
||||||
self.save(update_fields=["state"])
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_completed(self):
|
def is_completed(self):
|
||||||
"""
|
|
||||||
Computed boolean property indicating whether the trade acceptance has been
|
|
||||||
marked as thanked by one or both parties.
|
|
||||||
"""
|
|
||||||
return self.state in {
|
return self.state in {
|
||||||
self.AcceptanceState.RECEIVED,
|
self.AcceptanceState.RECEIVED,
|
||||||
self.AcceptanceState.THANKED_BY_INITIATOR,
|
self.AcceptanceState.THANKED_BY_INITIATOR,
|
||||||
|
|
@ -247,18 +137,10 @@ class TradeAcceptance(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_thanked(self):
|
def is_thanked(self):
|
||||||
"""
|
|
||||||
Computed boolean property indicating whether the trade acceptance has been
|
|
||||||
marked as thanked by one or both parties.
|
|
||||||
"""
|
|
||||||
return self.state == self.AcceptanceState.THANKED_BY_BOTH
|
return self.state == self.AcceptanceState.THANKED_BY_BOTH
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_rejected(self):
|
def is_rejected(self):
|
||||||
"""
|
|
||||||
Computed boolean property that is True if the trade acceptance has been rejected
|
|
||||||
by either the initiator or the acceptor.
|
|
||||||
"""
|
|
||||||
return self.state in {
|
return self.state in {
|
||||||
self.AcceptanceState.REJECTED_BY_INITIATOR,
|
self.AcceptanceState.REJECTED_BY_INITIATOR,
|
||||||
self.AcceptanceState.REJECTED_BY_ACCEPTOR,
|
self.AcceptanceState.REJECTED_BY_ACCEPTOR,
|
||||||
|
|
@ -266,41 +148,24 @@ class TradeAcceptance(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_completed_or_rejected(self):
|
def is_completed_or_rejected(self):
|
||||||
"""
|
|
||||||
Computed boolean property that is True if the trade acceptance is either completed
|
|
||||||
(i.e., thanked) or rejected.
|
|
||||||
"""
|
|
||||||
return self.is_completed or self.is_rejected
|
return self.is_completed or self.is_rejected
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
"""
|
|
||||||
Computed boolean property that is True if the trade acceptance is still active,
|
|
||||||
meaning it is neither completed (thanked) nor rejected.
|
|
||||||
"""
|
|
||||||
return not self.is_completed_or_rejected
|
return not self.is_completed_or_rejected
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""
|
# Validate that the requested and offered cards exist in the through tables.
|
||||||
Validate that:
|
|
||||||
- The requested_card is associated with the trade offer's have_cards (via the through model).
|
|
||||||
- The offered_card is associated with the trade offer's want_cards (via the through model).
|
|
||||||
- The trade offer is not already closed.
|
|
||||||
- The total number of active acceptances for each chosen card does not exceed the available quantity.
|
|
||||||
"""
|
|
||||||
# Validate that requested_card is in trade_offer.have_cards
|
|
||||||
try:
|
try:
|
||||||
have_through_obj = self.trade_offer.trade_offer_have_cards.get(card=self.requested_card)
|
have_through_obj = self.trade_offer.trade_offer_have_cards.get(card=self.requested_card)
|
||||||
except TradeOfferHaveCard.DoesNotExist:
|
except TradeOfferHaveCard.DoesNotExist:
|
||||||
raise ValidationError("The requested card must be one of the trade offer's available cards (have_cards).")
|
raise ValidationError("The requested card must be one of the trade offer's available cards (have_cards).")
|
||||||
|
|
||||||
# Validate that offered_card is in trade_offer.want_cards
|
|
||||||
try:
|
try:
|
||||||
want_through_obj = self.trade_offer.trade_offer_want_cards.get(card=self.offered_card)
|
want_through_obj = self.trade_offer.trade_offer_want_cards.get(card=self.offered_card)
|
||||||
except TradeOfferWantCard.DoesNotExist:
|
except TradeOfferWantCard.DoesNotExist:
|
||||||
raise ValidationError("The offered card must be one of the trade offer's requested cards (want_cards).")
|
raise ValidationError("The offered card must be one of the trade offer's requested cards (want_cards).")
|
||||||
|
|
||||||
# For new acceptances, do not allow creation if the trade offer is closed.
|
|
||||||
if not self.pk and self.trade_offer.is_closed:
|
if not self.pk and self.trade_offer.is_closed:
|
||||||
raise ValidationError("This trade offer is closed. No more acceptances are allowed.")
|
raise ValidationError("This trade offer is closed. No more acceptances are allowed.")
|
||||||
|
|
||||||
|
|
@ -317,20 +182,15 @@ class TradeAcceptance(models.Model):
|
||||||
if self.pk:
|
if self.pk:
|
||||||
active_acceptances = active_acceptances.exclude(pk=self.pk)
|
active_acceptances = active_acceptances.exclude(pk=self.pk)
|
||||||
|
|
||||||
# Count active acceptances for the requested card.
|
|
||||||
requested_count = active_acceptances.filter(requested_card=self.requested_card).count()
|
requested_count = active_acceptances.filter(requested_card=self.requested_card).count()
|
||||||
if requested_count >= have_through_obj.quantity:
|
if requested_count >= have_through_obj.quantity:
|
||||||
raise ValidationError("This requested card has been fully accepted.")
|
raise ValidationError("This requested card has been fully accepted.")
|
||||||
|
|
||||||
# Count active acceptances for the offered card.
|
|
||||||
offered_count = active_acceptances.filter(offered_card=self.offered_card).count()
|
offered_count = active_acceptances.filter(offered_card=self.offered_card).count()
|
||||||
if offered_count >= want_through_obj.quantity:
|
if offered_count >= want_through_obj.quantity:
|
||||||
raise ValidationError("This offered card has already been fully used.")
|
raise ValidationError("This offered card has already been fully used.")
|
||||||
|
|
||||||
def get_step_number(self):
|
def get_step_number(self):
|
||||||
"""
|
|
||||||
Return the step number for the current state.
|
|
||||||
"""
|
|
||||||
if self.state in [
|
if self.state in [
|
||||||
self.AcceptanceState.THANKED_BY_INITIATOR,
|
self.AcceptanceState.THANKED_BY_INITIATOR,
|
||||||
self.AcceptanceState.THANKED_BY_ACCEPTOR,
|
self.AcceptanceState.THANKED_BY_ACCEPTOR,
|
||||||
|
|
@ -346,17 +206,12 @@ class TradeAcceptance(models.Model):
|
||||||
]:
|
]:
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
# .choices is a list of tuples, so we need to find the index of the tuple that contains the state.
|
return next(index for index, choice in enumerate(self.AcceptanceState.choices) if choice[0] == self.state) + 1
|
||||||
return (next(index for index, choice in enumerate(self.AcceptanceState.choices) if choice[0] == self.state) + 1)
|
|
||||||
|
|
||||||
def update_state(self, new_state, user):
|
def update_state(self, new_state, user):
|
||||||
"""
|
|
||||||
Update the trade acceptance state.
|
|
||||||
"""
|
|
||||||
if new_state not in [choice[0] for choice in self.AcceptanceState.choices]:
|
if new_state not in [choice[0] for choice in self.AcceptanceState.choices]:
|
||||||
raise ValueError(f"'{new_state}' is not a valid state.")
|
raise ValueError(f"'{new_state}' is not a valid state.")
|
||||||
|
|
||||||
# Terminal states: no further transitions allowed.
|
|
||||||
if self.state in [
|
if self.state in [
|
||||||
self.AcceptanceState.THANKED_BY_BOTH,
|
self.AcceptanceState.THANKED_BY_BOTH,
|
||||||
self.AcceptanceState.REJECTED_BY_INITIATOR,
|
self.AcceptanceState.REJECTED_BY_INITIATOR,
|
||||||
|
|
@ -364,10 +219,7 @@ class TradeAcceptance(models.Model):
|
||||||
]:
|
]:
|
||||||
raise ValueError(f"No transitions allowed from the terminal state '{self.state}'.")
|
raise ValueError(f"No transitions allowed from the terminal state '{self.state}'.")
|
||||||
|
|
||||||
allowed = [x for x,y in self.get_allowed_state_transitions(user)]
|
allowed = [x for x, y in self.get_allowed_state_transitions(user)]
|
||||||
print(allowed)
|
|
||||||
print(new_state)
|
|
||||||
|
|
||||||
if new_state not in allowed:
|
if new_state not in allowed:
|
||||||
raise ValueError(f"Transition from {self.state} to {new_state} is not allowed.")
|
raise ValueError(f"Transition from {self.state} to {new_state} is not allowed.")
|
||||||
|
|
||||||
|
|
@ -378,7 +230,6 @@ class TradeAcceptance(models.Model):
|
||||||
is_new = self.pk is None
|
is_new = self.pk is None
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
if is_new and not self.hash:
|
if is_new and not self.hash:
|
||||||
# Append "y" so all trade acceptance hashes differ from trade offers.
|
|
||||||
self.hash = hashlib.md5((str(self.id) + "y").encode("utf-8")).hexdigest()[:8] + "y"
|
self.hash = hashlib.md5((str(self.id) + "y").encode("utf-8")).hexdigest()[:8] + "y"
|
||||||
super().save(update_fields=["hash"])
|
super().save(update_fields=["hash"])
|
||||||
|
|
||||||
|
|
@ -389,11 +240,6 @@ class TradeAcceptance(models.Model):
|
||||||
f"offered_card={self.offered_card}, state={self.state})")
|
f"offered_card={self.offered_card}, state={self.state})")
|
||||||
|
|
||||||
def get_allowed_state_transitions(self, user):
|
def get_allowed_state_transitions(self, user):
|
||||||
"""
|
|
||||||
Returns a list of allowed state transitions as tuples (value, display_label)
|
|
||||||
based on the current state of this trade acceptance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.trade_offer.initiated_by in user.friend_codes.all():
|
if self.trade_offer.initiated_by in user.friend_codes.all():
|
||||||
allowed_transitions = {
|
allowed_transitions = {
|
||||||
self.AcceptanceState.ACCEPTED: {
|
self.AcceptanceState.ACCEPTED: {
|
||||||
|
|
@ -440,6 +286,4 @@ class TradeAcceptance(models.Model):
|
||||||
allowed_transitions = {}
|
allowed_transitions = {}
|
||||||
|
|
||||||
allowed = allowed_transitions.get(self.state, {})
|
allowed = allowed_transitions.get(self.state, {})
|
||||||
|
|
||||||
# Return as a list of tuples (state_value, human-readable label)
|
|
||||||
return [(state, self.AcceptanceState(state).label) for state in allowed]
|
return [(state, self.AcceptanceState(state).label) for state in allowed]
|
||||||
|
|
|
||||||
|
|
@ -22,23 +22,55 @@ def validate_want_cards_rarity(sender, instance, action, **kwargs):
|
||||||
if action == "post_add":
|
if action == "post_add":
|
||||||
check_trade_offer_rarity(instance)
|
check_trade_offer_rarity(instance)
|
||||||
|
|
||||||
@receiver(post_save, sender=TradeOfferHaveCard)
|
ACTIVE_STATES = [
|
||||||
@receiver(post_delete, sender=TradeOfferHaveCard)
|
TradeAcceptance.AcceptanceState.ACCEPTED,
|
||||||
def update_aggregates_from_have_card(sender, instance, **kwargs):
|
TradeAcceptance.AcceptanceState.SENT,
|
||||||
trade_offer = instance.trade_offer
|
TradeAcceptance.AcceptanceState.RECEIVED,
|
||||||
if trade_offer and hasattr(trade_offer, 'update_aggregates'):
|
TradeAcceptance.AcceptanceState.THANKED_BY_INITIATOR,
|
||||||
trade_offer.update_aggregates()
|
TradeAcceptance.AcceptanceState.THANKED_BY_ACCEPTOR,
|
||||||
|
TradeAcceptance.AcceptanceState.THANKED_BY_BOTH,
|
||||||
|
]
|
||||||
|
|
||||||
@receiver(post_save, sender=TradeOfferWantCard)
|
def update_qty_for_trade_offer(trade_offer, card, side):
|
||||||
@receiver(post_delete, sender=TradeOfferWantCard)
|
if side == 'have':
|
||||||
def update_aggregates_from_want_card(sender, instance, **kwargs):
|
count = TradeAcceptance.objects.filter(
|
||||||
|
trade_offer=trade_offer,
|
||||||
|
requested_card=card,
|
||||||
|
state__in=ACTIVE_STATES
|
||||||
|
).count()
|
||||||
|
TradeOfferHaveCard.objects.filter(
|
||||||
|
trade_offer=trade_offer,
|
||||||
|
card=card
|
||||||
|
).update(qty_accepted=count)
|
||||||
|
|
||||||
|
if count >= TradeOfferHaveCard.objects.filter(trade_offer=trade_offer, card=card).first().quantity:
|
||||||
|
trade_offer.is_closed = True
|
||||||
|
trade_offer.save(update_fields=["is_closed"])
|
||||||
|
elif side == 'want':
|
||||||
|
count = TradeAcceptance.objects.filter(
|
||||||
|
trade_offer=trade_offer,
|
||||||
|
offered_card=card,
|
||||||
|
state__in=ACTIVE_STATES
|
||||||
|
).count()
|
||||||
|
TradeOfferWantCard.objects.filter(
|
||||||
|
trade_offer=trade_offer,
|
||||||
|
card=card
|
||||||
|
).update(qty_accepted=count)
|
||||||
|
|
||||||
|
if count >= TradeOfferWantCard.objects.filter(trade_offer=trade_offer, card=card).first().quantity:
|
||||||
|
trade_offer.is_closed = True
|
||||||
|
trade_offer.save(update_fields=["is_closed"])
|
||||||
|
|
||||||
|
|
||||||
|
def update_all_qty(instance):
|
||||||
trade_offer = instance.trade_offer
|
trade_offer = instance.trade_offer
|
||||||
if trade_offer and hasattr(trade_offer, 'update_aggregates'):
|
update_qty_for_trade_offer(trade_offer, instance.requested_card, 'have')
|
||||||
trade_offer.update_aggregates()
|
update_qty_for_trade_offer(trade_offer, instance.offered_card, 'want')
|
||||||
|
|
||||||
@receiver(post_save, sender=TradeAcceptance)
|
@receiver(post_save, sender=TradeAcceptance)
|
||||||
|
def trade_acceptance_post_save(sender, instance, **kwargs):
|
||||||
|
update_all_qty(instance)
|
||||||
|
|
||||||
@receiver(post_delete, sender=TradeAcceptance)
|
@receiver(post_delete, sender=TradeAcceptance)
|
||||||
def update_aggregates_from_acceptance(sender, instance, **kwargs):
|
def trade_acceptance_post_delete(sender, instance, **kwargs):
|
||||||
trade_offer = instance.trade_offer
|
update_all_qty(instance)
|
||||||
if trade_offer and hasattr(trade_offer, 'update_aggregates'):
|
|
||||||
trade_offer.update_aggregates()
|
|
||||||
|
|
@ -90,23 +90,11 @@ class TradeOfferAllListView(ListView):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.order_by("-updated_at")
|
.order_by("-updated_at")
|
||||||
.annotate(
|
|
||||||
is_active=Case(
|
|
||||||
When(
|
|
||||||
manually_closed=False,
|
|
||||||
total_have_quantity__gt=F('total_have_accepted'),
|
|
||||||
total_want_quantity__gt=F('total_want_accepted'),
|
|
||||||
then=Value(True)
|
|
||||||
),
|
|
||||||
default=Value(False),
|
|
||||||
output_field=BooleanField()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if show_closed:
|
if show_closed:
|
||||||
queryset = queryset.filter(is_active=False)
|
queryset = queryset.filter(is_closed=True)
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(is_active=True)
|
queryset = queryset.filter(is_closed=False)
|
||||||
|
|
||||||
offers_page = request.GET.get("offers_page")
|
offers_page = request.GET.get("offers_page")
|
||||||
offers_paginator = Paginator(queryset, 10)
|
offers_paginator = Paginator(queryset, 10)
|
||||||
|
|
@ -135,18 +123,6 @@ class TradeOfferMyListView(LoginRequiredMixin, ListView):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.order_by("-updated_at")
|
.order_by("-updated_at")
|
||||||
.annotate(
|
|
||||||
is_active=Case(
|
|
||||||
When(
|
|
||||||
manually_closed=False,
|
|
||||||
total_have_quantity__gt=F('total_have_accepted'),
|
|
||||||
total_want_quantity__gt=F('total_want_accepted'),
|
|
||||||
then=Value(True)
|
|
||||||
),
|
|
||||||
default=Value(False),
|
|
||||||
output_field=BooleanField()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
@ -173,9 +149,9 @@ class TradeOfferMyListView(LoginRequiredMixin, ListView):
|
||||||
|
|
||||||
queryset = self.get_queryset().filter(initiated_by=selected_friend_code)
|
queryset = self.get_queryset().filter(initiated_by=selected_friend_code)
|
||||||
if show_closed:
|
if show_closed:
|
||||||
queryset = queryset.filter(is_active=False)
|
queryset = queryset.filter(is_closed=True)
|
||||||
else:
|
else:
|
||||||
queryset = queryset.filter(is_active=True)
|
queryset = queryset.filter(is_closed=False)
|
||||||
|
|
||||||
offers_page = request.GET.get("offers_page")
|
offers_page = request.GET.get("offers_page")
|
||||||
offers_paginator = Paginator(queryset, 10)
|
offers_paginator = Paginator(queryset, 10)
|
||||||
|
|
@ -267,8 +243,8 @@ class TradeOfferDeleteView(LoginRequiredMixin, DeleteView):
|
||||||
return self.render_to_response(context)
|
return self.render_to_response(context)
|
||||||
else:
|
else:
|
||||||
if trade_offer.acceptances.count() > 0:
|
if trade_offer.acceptances.count() > 0:
|
||||||
trade_offer.manually_closed = True
|
trade_offer.is_closed = True
|
||||||
trade_offer.save(update_fields=["manually_closed"])
|
trade_offer.save(update_fields=["is_closed"])
|
||||||
messages.success(request, "Trade offer has been marked as closed.")
|
messages.success(request, "Trade offer has been marked as closed.")
|
||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
else:
|
else:
|
||||||
|
|
@ -330,9 +306,7 @@ class TradeOfferSearchView(LoginRequiredMixin, ListView):
|
||||||
return TradeOffer.objects.none()
|
return TradeOffer.objects.none()
|
||||||
|
|
||||||
qs = TradeOffer.objects.filter(
|
qs = TradeOffer.objects.filter(
|
||||||
manually_closed=False,
|
is_closed=False,
|
||||||
total_have_accepted__lt=F("total_have_quantity"),
|
|
||||||
total_want_accepted__lt=F("total_want_quantity")
|
|
||||||
).exclude(initiated_by__in=self.request.user.friend_codes.all())
|
).exclude(initiated_by__in=self.request.user.friend_codes.all())
|
||||||
|
|
||||||
# Chain filters for offered selections (i.e. the user "has" cards).
|
# Chain filters for offered selections (i.e. the user "has" cards).
|
||||||
|
|
@ -408,13 +382,6 @@ class TradeOfferDetailView(LoginRequiredMixin, DetailView):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.annotate(
|
|
||||||
is_active=Case(
|
|
||||||
When(manually_closed=False, then=Value(True)),
|
|
||||||
default=Value(False),
|
|
||||||
output_field=BooleanField()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
@ -436,8 +403,6 @@ class TradeOfferDetailView(LoginRequiredMixin, DetailView):
|
||||||
# Option 1: Filter active acceptances using the queryset lookup.
|
# Option 1: Filter active acceptances using the queryset lookup.
|
||||||
context["active_acceptances"] = trade_offer.acceptances.exclude(state__in=terminal_states)
|
context["active_acceptances"] = trade_offer.acceptances.exclude(state__in=terminal_states)
|
||||||
|
|
||||||
# Option 2: Or filter using the computed property (if you prefer to work with Python iterables):
|
|
||||||
# context["active_acceptances"] = [acc for acc in trade_offer.acceptances.all() if acc.is_active]
|
|
||||||
|
|
||||||
user_friend_codes = self.request.user.friend_codes.all()
|
user_friend_codes = self.request.user.friend_codes.all()
|
||||||
|
|
||||||
|
|
@ -466,7 +431,7 @@ class TradeAcceptanceCreateView(LoginRequiredMixin, CreateView):
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.trade_offer = self.get_trade_offer()
|
self.trade_offer = self.get_trade_offer()
|
||||||
if self.trade_offer.initiated_by_id in request.user.friend_codes.values_list("id", flat=True) or not self.trade_offer.is_active:
|
if self.trade_offer.initiated_by_id in request.user.friend_codes.values_list("id", flat=True) or self.trade_offer.is_closed:
|
||||||
raise PermissionDenied("You cannot accept this trade offer.")
|
raise PermissionDenied("You cannot accept this trade offer.")
|
||||||
if not request.user.friend_codes.exists():
|
if not request.user.friend_codes.exists():
|
||||||
raise PermissionDenied("No friend codes available for your account.")
|
raise PermissionDenied("No friend codes available for your account.")
|
||||||
|
|
@ -485,13 +450,6 @@ class TradeAcceptanceCreateView(LoginRequiredMixin, CreateView):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.annotate(
|
|
||||||
is_active=Case(
|
|
||||||
When(manually_closed=False, then=Value(True)),
|
|
||||||
default=Value(False),
|
|
||||||
output_field=BooleanField()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.get(pk=self.kwargs['offer_pk'])
|
.get(pk=self.kwargs['offer_pk'])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -520,10 +478,11 @@ class TradeAcceptanceUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if self.object.accepted_by_id not in request.user.friend_codes.values_list("id", flat=True):
|
|
||||||
raise PermissionDenied("You are not authorized to update this acceptance.")
|
|
||||||
if not request.user.friend_codes.exists():
|
if not request.user.friend_codes.exists():
|
||||||
raise PermissionDenied("No friend codes available for your account.")
|
raise PermissionDenied("No friend codes available for your account.")
|
||||||
|
friend_codes = request.user.friend_codes.values_list("id", flat=True)
|
||||||
|
if self.object.accepted_by_id not in friend_codes and self.object.trade_offer.initiated_by_id not in friend_codes:
|
||||||
|
raise PermissionDenied("You are not authorized to update this acceptance.")
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue