Add in_game_name field to FriendCode model
This commit is contained in:
parent
4792906907
commit
bc181b12d9
19 changed files with 113 additions and 56 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ class HomePageView(TemplateView):
|
|||
"want_cards__decks",
|
||||
"want_cards__rarity",
|
||||
"want_cards__cardset",
|
||||
"acceptances"
|
||||
"acceptances",
|
||||
|
||||
)
|
||||
.select_related("initiated_by__user")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue