Add in_game_name field to FriendCode model

This commit is contained in:
badblocks 2025-03-14 09:50:49 -07:00
parent 4792906907
commit bc181b12d9
19 changed files with 113 additions and 56 deletions

View file

@ -2,6 +2,7 @@ from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser, FriendCode
from allauth.account.forms import SignupForm
from crispy_tailwind.tailwind import CSSContainer
class CustomUserChangeForm(UserChangeForm):
@ -12,7 +13,7 @@ class CustomUserChangeForm(UserChangeForm):
class FriendCodeForm(forms.ModelForm):
class Meta:
model = FriendCode
fields = ["friend_code"]
fields = ["friend_code", "in_game_name"]
def clean_friend_code(self):
friend_code = self.cleaned_data.get("friend_code", "").strip()
@ -30,12 +31,32 @@ class CustomUserCreationForm(SignupForm):
model = CustomUser
fields = ['email', 'username', 'friend_code']
email = forms.EmailField(
required=True,
label="Email",
widget=forms.TextInput(attrs={'placeholder': 'Email', 'class':'dark:bg-base-100'})
)
username = forms.CharField(
max_length=24,
required=True,
label="Username",
widget=forms.TextInput(attrs={'placeholder': 'Username', 'class':'dark:bg-base-100'})
)
friend_code = forms.CharField(
max_length=19,
required=True,
label="Friend Code",
help_text="Enter your friend code in the format XXXX-XXXX-XXXX-XXXX.",
widget=forms.TextInput(attrs={'placeholder': 'XXXX-XXXX-XXXX-XXXX'})
widget=forms.TextInput(attrs={'placeholder': 'XXXX-XXXX-XXXX-XXXX', 'class':'dark:bg-base-100'})
)
in_game_name = forms.CharField(
max_length=16,
required=True,
label="In-Game Name",
help_text="Enter your in-game name.",
widget=forms.TextInput(attrs={'placeholder': 'In-Game Name', 'class':'dark:bg-base-100'})
)
def __init__(self, *args, **kwargs):

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-13 01:46
# Generated by Django 5.1.2 on 2025-03-14 05:35
import django.contrib.auth.models
import django.contrib.auth.validators
@ -48,6 +48,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('friend_code', models.CharField(max_length=19)),
('in_game_name', models.CharField(max_length=16)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='friend_codes', to=settings.AUTH_USER_MODEL)),

View file

@ -27,6 +27,7 @@ class CustomUser(AbstractUser):
class FriendCode(models.Model):
friend_code = models.CharField(max_length=19)
in_game_name = models.CharField(max_length=16, null=False, blank=False)
user = models.ForeignKey(CustomUser, on_delete=models.PROTECT, related_name='friend_codes')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

View file

@ -89,7 +89,7 @@ class DeleteFriendCodeView(LoginRequiredMixin, DeleteView):
return redirect(self.success_url)
# Also check if this friend code is referenced by any trade offer.
if self.object.initiated_by.exists() or self.object.accepted_by.exists():
if self.object.initiated_trade_offers.exists() or self.object.trade_acceptances.exists():
messages.error(
request,
"Cannot remove this friend code because there are existing trade offers associated with it."

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-13 01:46
# Generated by Django 5.1.2 on 2025-03-14 05:35
import django.db.models.deletion
from django.db import migrations, models

View file

@ -51,7 +51,8 @@ class HomePageView(TemplateView):
"want_cards__decks",
"want_cards__rarity",
"want_cards__cardset",
"acceptances"
"acceptances",
)
.select_related("initiated_by__user")
)

View file

@ -42,6 +42,7 @@
"pk": 1,
"fields": {
"friend_code": "9167-8051-9691-8032",
"in_game_name": "badblocks",
"user": 1,
"created_at": "2025-03-13T04:21:05.166Z",
"updated_at": "2025-03-13T04:21:05.166Z"
@ -52,6 +53,7 @@
"pk": 2,
"fields": {
"friend_code": "1234-2938-7848-7636",
"in_game_name": "backrolls",
"user": 2,
"created_at": "2025-03-13T04:52:29.166Z",
"updated_at": "2025-03-13T04:52:29.166Z"

View file

@ -1,8 +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 class="alert alert-{{ message.tags }} bg-base-100 mb-4 flex justify-between items-center">
<span>{{ message }}</span>
<button class="btn btn-xs btn-circle" onclick="this.parentElement.remove();" aria-label="Dismiss"></button>
</div>
{% endfor %}
</div>

View file

@ -15,7 +15,7 @@
{% trans "Friend Codes" %}
</a>
<a href="{% url 'account_logout' %}" class="btn btn-warning">
{% trans "Logout" %}
{% trans "Sign Out" %}
</a>
</div>
</div>

View file

@ -1,6 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block head_title %}{% trans "Sign Up" %}{% endblock %}
{% block content %}
@ -34,6 +35,11 @@
{{ form.friend_code }}
{{ form.friend_code.errors }}
</div>
<div>
<label for="{{ form.in_game_name.id_for_label }}" class="block font-medium">{{ form.in_game_name.label }}</label>
{{ form.in_game_name }}
{{ form.in_game_name.errors }}
</div>
<button type="submit" class="btn btn-primary w-full">{% trans "Sign Up" %}</button>
</form>
<div class="mt-4 text-center">

View file

@ -24,7 +24,7 @@
})();
</script>
<title>{% block title %}PᴋMɴ Trade Club{% endblock title %}</title>
<title>[PᴋMɴ Trade Club] {% block title %}{% endblock title %}</title>
<link rel="shortcut icon" href="{% static 'images/favicon.ico' %}">
<!-- Choices.js -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/choices.js@11.0.6/public/assets/styles/choices.min.css" />
@ -113,7 +113,7 @@
Friend Codes
</a>
</li>
<li><a href="{% url 'account_logout' %}">Logout</a></li>
<li><a href="{% url 'account_logout' %}">Sign Out</a></li>
</ul>
</div>
</div>

View file

@ -1,5 +1,5 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% load crispy_forms_tags i18n %}
{% block title %}Add Friend Code{% endblock %}
@ -8,8 +8,18 @@
<h1 class="text-3xl font-bold mb-4">Add Friend Code</h1>
<form method="post" class="space-y-4">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn btn-primary w-full">Add Friend Code</button>
{{ form.non_field_errors }}
<div>
<label for="{{ form.friend_code.id_for_label }}" class="block font-medium">{{ form.friend_code.label }}</label>
{{ form.friend_code }}
{{ form.friend_code.errors }}
</div>
<div>
<label for="{{ form.in_game_name.id_for_label }}" class="block font-medium">{{ form.in_game_name.label }}</label>
{{ form.in_game_name }}
{{ form.in_game_name.errors }}
</div>
<button type="submit" class="btn btn-primary w-full">{% trans "Add Friend Code" %}</button>
</form>
<div class="mt-4">
@ -19,11 +29,11 @@
<!-- Include Cleave Zen from a CDN -->
<script src="https://unpkg.com/cleave-zen@0.0.17/dist/cleave-zen.umd.js"></script>
<script>
<script defer>
document.addEventListener('DOMContentLoaded', function(){
// Initialize Cleave Zen on the friend code input field.
// Make sure that the input ID is correct (e.g., provided by Django's widget rendering).
new CleaveZen('#id_friend_code', {
cleaveZen('#id_friend_code', {
delimiters: ['-', '-', '-'], // Inserts dashes between the blocks.
blocks: [4, 4, 4, 4],
numericOnly: true

View file

@ -9,19 +9,21 @@
{% if friend_codes %}
<ul class="space-y-2">
{% 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-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>
<span class="font-mono">{{ code.friend_code }}</span>
{% if user.default_friend_code and code.id == user.default_friend_code.id %}
<span class="badge badge-success ml-2">Default</span>
{% endif %}
<li class="flex flex-col sm:flex-row items-start sm: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 class="flex-2">
<span>{{ code.in_game_name }}</span>
</div>
<div class="flex items-center space-x-2">
<div class="flex-3 text-center">
<span class="font-mono">{{ code.friend_code }}</span>
</div>
<div class="flex-2 flex items-center space-x-2 self-end">
{% if user.default_friend_code and not code.id == user.default_friend_code.id %}
<form method="post" action="{% url 'change_default_friend_code' code.id %}">
{% csrf_token %}
<button type="submit" class="btn btn-secondary btn-sm">Set as Default</button>
</form>
{% elif user.default_friend_code and code.id == user.default_friend_code.id %}
<span class="badge badge-success ml-2">Default</span>
{% endif %}
<a href="{% url 'delete_friend_code' code.id %}" class="btn btn-error btn-sm">Delete</a>
</div>

View file

@ -27,11 +27,9 @@
<button type="submit" class="btn btn-primary grow">
Find a Trade Offer
</button>
{% if user.is_authenticated %}
<a href="{% url 'trade_offer_create' %}" id="createTradeOfferBtn" class="btn btn-secondary grow">
Create Trade Offer
</a>
{% endif %}
<a href="{% url 'trade_offer_create' %}" id="createTradeOfferBtn" class="btn btn-secondary grow">
Create Trade Offer
</a>
</div>
</form>
</section>

View file

@ -4,7 +4,7 @@ Expected variables:
- friend_codes: A list or QuerySet of FriendCode objects.
- 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").
- label (optional): The label text (default "Friend Code").
- label (optional): The label text (default None).
{% endcomment %}
{% with field_name=field_name|default:"friend_code" label=label|default:"" %}
@ -19,7 +19,7 @@ Expected variables:
<select id="{{ field_name }}" name="{{ field_name }}" class="select select-bordered w-full" @change="$el.form.submit()">
{% for code in friend_codes %}
<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.in_game_name }}
</option>
{% endfor %}
</select>

View file

@ -98,8 +98,8 @@ if (!window.tradeOfferCard) {
</div>
</div>
</a>
{% 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 h-5">
{% if have_cards_available|length > 1 or want_cards_available|length > 1 %}
<svg @click="badgeExpanded = !badgeExpanded"
x-bind:class="{ 'rotate-180': badgeExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer"
@ -107,8 +107,8 @@ if (!window.tradeOfferCard) {
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" />
</svg>
</div>
{% endif %}
{% endif %}
</div>
</div>
<div class="self-end">
<div class="flex justify-between px-2 pb-2">
@ -214,8 +214,8 @@ if (!window.tradeOfferCard) {
{% endfor %}
</div>
</div>
{% if offer.acceptances.all|length > 1 %}
<div class="flex justify-center my-1">
<div class="flex justify-center my-1 h-5">
{% if offer.acceptances.all|length > 1 %}
<svg @click="acceptanceExpanded = !acceptanceExpanded"
x-bind:class="{ 'rotate-180': acceptanceExpanded }"
class="transition-transform duration-500 h-5 w-5 cursor-pointer"
@ -223,8 +223,8 @@ if (!window.tradeOfferCard) {
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7" />
</svg>
{% endif %}
</div>
{% endif %}
</div>
<div class="flex justify-between px-2 pb-2 self-end">
<!-- Back-to-front flip button -->

View file

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2025-03-13 01:46
# Generated by Django 5.1.2 on 2025-03-14 05:35
import django.db.models.deletion
from django.db import migrations, models

View file

@ -9,8 +9,6 @@ def render_trade_offer(context, offer):
Groups acceptances for each card on both the have and want sides.
"""
# Use the already prefetched acceptances.
acceptances = offer.acceptances.all()
have_cards_available = []
want_cards_available = []

View file

@ -23,11 +23,6 @@ class TradeOfferCreateView(LoginRequiredMixin, CreateView):
template_name = "trades/trade_offer_create.html"
success_url = reverse_lazy("trade_offer_list")
def dispatch(self, request, *args, **kwargs):
if not request.user.friend_codes.exists():
raise PermissionDenied("No friend codes available for your account.")
return super().dispatch(request, *args, **kwargs)
def get_form(self, form_class=None):
form = super().get_form(form_class)
# Restrict the 'initiated_by' choices to friend codes owned by the logged-in user.
@ -86,8 +81,21 @@ class TradeOfferAllListView(ListView):
'trade_offer_want_cards__card',
Prefetch(
'acceptances',
queryset=TradeAcceptance.objects.select_related('accepted_by', 'requested_card', 'offered_card')
)
queryset=TradeAcceptance.objects.select_related('accepted_by', 'requested_card', 'offered_card').prefetch_related(
'requested_card__decks',
'offered_card__decks',
'requested_card__rarity',
'offered_card__rarity',
'requested_card__cardset',
'offered_card__cardset',
)
),
'trade_offer_have_cards__card__decks',
'trade_offer_want_cards__card__decks',
'trade_offer_have_cards__card__rarity',
'trade_offer_want_cards__card__rarity',
'trade_offer_have_cards__card__cardset',
'trade_offer_want_cards__card__cardset'
)
.order_by("-updated_at")
)
@ -115,8 +123,21 @@ class TradeOfferAllListView(ListView):
'trade_offer_want_cards__card',
Prefetch(
'acceptances',
queryset=TradeAcceptance.objects.select_related('accepted_by', 'requested_card', 'offered_card')
)
queryset=TradeAcceptance.objects.select_related('accepted_by', 'requested_card', 'offered_card').prefetch_related(
'requested_card__decks',
'offered_card__decks',
'requested_card__rarity',
'offered_card__rarity',
'requested_card__cardset',
'offered_card__cardset',
)
),
'trade_offer_have_cards__card__decks',
'trade_offer_want_cards__card__decks',
'trade_offer_have_cards__card__rarity',
'trade_offer_want_cards__card__rarity',
'trade_offer_have_cards__card__cardset',
'trade_offer_want_cards__card__cardset'
)
.order_by("-updated_at")
)
@ -314,7 +335,7 @@ class TradeOfferDeleteView(LoginRequiredMixin, DeleteView):
messages.success(request, "Trade offer has been deleted.")
return super().delete(request, *args, **kwargs)
class TradeOfferSearchView(LoginRequiredMixin, ListView):
class TradeOfferSearchView(ListView):
"""
Reworked trade offer search view using POST.
@ -427,11 +448,6 @@ class TradeOfferDetailView(LoginRequiredMixin, DetailView):
model = TradeOffer
template_name = "trades/trade_offer_detail.html"
def dispatch(self, request, *args, **kwargs):
if not request.user.friend_codes.exists():
raise PermissionDenied("No friend codes available for your account.")
return super().dispatch(request, *args, **kwargs)
def get_queryset(self):
return (
TradeOffer.objects.select_related('initiated_by')