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 django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser, FriendCode from .models import CustomUser, FriendCode
from allauth.account.forms import SignupForm from allauth.account.forms import SignupForm
from crispy_tailwind.tailwind import CSSContainer
class CustomUserChangeForm(UserChangeForm): class CustomUserChangeForm(UserChangeForm):
@ -12,7 +13,7 @@ class CustomUserChangeForm(UserChangeForm):
class FriendCodeForm(forms.ModelForm): class FriendCodeForm(forms.ModelForm):
class Meta: class Meta:
model = FriendCode model = FriendCode
fields = ["friend_code"] fields = ["friend_code", "in_game_name"]
def clean_friend_code(self): def clean_friend_code(self):
friend_code = self.cleaned_data.get("friend_code", "").strip() friend_code = self.cleaned_data.get("friend_code", "").strip()
@ -30,12 +31,32 @@ class CustomUserCreationForm(SignupForm):
model = CustomUser model = CustomUser
fields = ['email', 'username', 'friend_code'] 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( friend_code = forms.CharField(
max_length=19, max_length=19,
required=True, required=True,
label="Friend Code", label="Friend Code",
help_text="Enter your friend code in the format XXXX-XXXX-XXXX-XXXX.", 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): 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.models
import django.contrib.auth.validators import django.contrib.auth.validators
@ -48,6 +48,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')),
('friend_code', models.CharField(max_length=19)), ('friend_code', models.CharField(max_length=19)),
('in_game_name', models.CharField(max_length=16)),
('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)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='friend_codes', to=settings.AUTH_USER_MODEL)), ('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): class FriendCode(models.Model):
friend_code = models.CharField(max_length=19) 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') user = models.ForeignKey(CustomUser, on_delete=models.PROTECT, related_name='friend_codes')
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)

View file

@ -89,7 +89,7 @@ class DeleteFriendCodeView(LoginRequiredMixin, DeleteView):
return redirect(self.success_url) return redirect(self.success_url)
# Also check if this friend code is referenced by any trade offer. # 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( messages.error(
request, request,
"Cannot remove this friend code because there are existing trade offers associated with it." "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 import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models

View file

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

View file

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

View file

@ -1,8 +1,9 @@
{% if messages %} {% if messages %}
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }} bg-base-100 mb-4"> <div class="alert alert-{{ message.tags }} bg-base-100 mb-4 flex justify-between items-center">
{{ message }} <span>{{ message }}</span>
<button class="btn btn-xs btn-circle" onclick="this.parentElement.remove();" aria-label="Dismiss"></button>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View file

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

View file

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

View file

@ -24,7 +24,7 @@
})(); })();
</script> </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' %}"> <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" />
@ -113,7 +113,7 @@
Friend Codes Friend Codes
</a> </a>
</li> </li>
<li><a href="{% url 'account_logout' %}">Logout</a></li> <li><a href="{% url 'account_logout' %}">Sign Out</a></li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load crispy_forms_tags %} {% load crispy_forms_tags i18n %}
{% block title %}Add Friend Code{% endblock %} {% block title %}Add Friend Code{% endblock %}
@ -8,8 +8,18 @@
<h1 class="text-3xl font-bold mb-4">Add Friend Code</h1> <h1 class="text-3xl font-bold mb-4">Add Friend Code</h1>
<form method="post" class="space-y-4"> <form method="post" class="space-y-4">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form.non_field_errors }}
<button type="submit" class="btn btn-primary w-full">Add Friend Code</button> <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> </form>
<div class="mt-4"> <div class="mt-4">
@ -19,11 +29,11 @@
<!-- Include Cleave Zen from a CDN --> <!-- Include Cleave Zen from a CDN -->
<script src="https://unpkg.com/cleave-zen@0.0.17/dist/cleave-zen.umd.js"></script> <script src="https://unpkg.com/cleave-zen@0.0.17/dist/cleave-zen.umd.js"></script>
<script> <script defer>
document.addEventListener('DOMContentLoaded', function(){ document.addEventListener('DOMContentLoaded', function(){
// Initialize Cleave Zen on the friend code input field. // 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). // 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. delimiters: ['-', '-', '-'], // Inserts dashes between the blocks.
blocks: [4, 4, 4, 4], blocks: [4, 4, 4, 4],
numericOnly: true numericOnly: true

View file

@ -9,19 +9,21 @@
{% 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-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"> <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> <div class="flex-2">
<span class="font-mono">{{ code.friend_code }}</span> <span>{{ code.in_game_name }}</span>
{% if user.default_friend_code and code.id == user.default_friend_code.id %}
<span class="badge badge-success ml-2">Default</span>
{% endif %}
</div> </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 %} {% 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 %}"> <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>
</form> </form>
{% elif user.default_friend_code and code.id == user.default_friend_code.id %}
<span class="badge badge-success ml-2">Default</span>
{% endif %} {% endif %}
<a href="{% url 'delete_friend_code' code.id %}" class="btn btn-error btn-sm">Delete</a> <a href="{% url 'delete_friend_code' code.id %}" class="btn btn-error btn-sm">Delete</a>
</div> </div>

View file

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

View file

@ -4,7 +4,7 @@ Expected variables:
- friend_codes: A list or QuerySet of FriendCode objects. - 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. - 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 None).
{% endcomment %} {% endcomment %}
{% with field_name=field_name|default:"friend_code" label=label|default:"" %} {% 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()"> <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 effective_friend_code and code.pk|stringformat:"s" == effective_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.in_game_name }}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>

View file

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

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 import django.db.models.deletion
from django.db import migrations, models 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. Groups acceptances for each card on both the have and want sides.
""" """
# Use the already prefetched acceptances.
acceptances = offer.acceptances.all()
have_cards_available = [] have_cards_available = []
want_cards_available = [] want_cards_available = []

View file

@ -23,11 +23,6 @@ class TradeOfferCreateView(LoginRequiredMixin, CreateView):
template_name = "trades/trade_offer_create.html" template_name = "trades/trade_offer_create.html"
success_url = reverse_lazy("trade_offer_list") 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): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
# Restrict the 'initiated_by' choices to friend codes owned by the logged-in user. # 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', 'trade_offer_want_cards__card',
Prefetch( Prefetch(
'acceptances', '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") .order_by("-updated_at")
) )
@ -115,8 +123,21 @@ class TradeOfferAllListView(ListView):
'trade_offer_want_cards__card', 'trade_offer_want_cards__card',
Prefetch( Prefetch(
'acceptances', '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") .order_by("-updated_at")
) )
@ -314,7 +335,7 @@ class TradeOfferDeleteView(LoginRequiredMixin, DeleteView):
messages.success(request, "Trade offer has been deleted.") messages.success(request, "Trade offer has been deleted.")
return super().delete(request, *args, **kwargs) return super().delete(request, *args, **kwargs)
class TradeOfferSearchView(LoginRequiredMixin, ListView): class TradeOfferSearchView(ListView):
""" """
Reworked trade offer search view using POST. Reworked trade offer search view using POST.
@ -427,11 +448,6 @@ class TradeOfferDetailView(LoginRequiredMixin, DetailView):
model = TradeOffer model = TradeOffer
template_name = "trades/trade_offer_detail.html" 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): def get_queryset(self):
return ( return (
TradeOffer.objects.select_related('initiated_by') TradeOffer.objects.select_related('initiated_by')