from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse_lazy from django.shortcuts import redirect, get_object_or_404 from django.views.generic import ListView, CreateView, DeleteView, View, TemplateView, UpdateView from accounts.models import FriendCode, CustomUser from accounts.forms import FriendCodeForm, UserSettingsForm from django.db.models import Case, When, Value, BooleanField from trades.models import TradeOffer, TradeAcceptance from django.core.exceptions import PermissionDenied from trades.mixins import FriendCodeRequiredMixin class ListFriendCodesView(LoginRequiredMixin, ListView): """ Display the current user's friend codes. """ model = FriendCode template_name = "friend_codes/list_friend_codes.html" context_object_name = "friend_codes" def get_queryset(self): # Get the default friend code's primary key if it exists. default_pk = getattr(self.request.user.default_friend_code, "pk", None) # Annotate each friend code with is_default=True if its pk matches. return self.request.user.friend_codes.all().annotate( is_default=Case( When(pk=default_pk, then=Value(True)), default=Value(False), output_field=BooleanField() ) ) class AddFriendCodeView(LoginRequiredMixin, CreateView): """ Add a new friend code for the current user. If the user does not yet have a default, the newly added code will automatically become the default. """ model = FriendCode form_class = FriendCodeForm template_name = "friend_codes/add_friend_code.html" success_url = reverse_lazy("list_friend_codes") def form_valid(self, form): form.instance.user = self.request.user messages.success(self.request, "Friend code added successfully.") return super().form_valid(form) class DeleteFriendCodeView(LoginRequiredMixin, DeleteView): """ Remove an existing friend code. Prevent deletion if the friend code is bound to any trade offers. Also, prevent deletion if the friend code is either the only one or is set as the default friend code. """ model = FriendCode template_name = "friend_codes/confirm_delete_friend_code.html" context_object_name = "friend_code" success_url = reverse_lazy("list_friend_codes") def get_queryset(self): # Only allow deletion of friend codes owned by the current user. return FriendCode.objects.filter(user=self.request.user) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) friend_code = self.get_object() user = self.request.user # Determine if the deletion should be disabled. disable_delete = False error_message = None if user.friend_codes.count() == 1: disable_delete = True error_message = "Cannot delete your only friend code." elif user.default_friend_code == friend_code: disable_delete = True error_message = ( "Cannot delete your default friend code. " "Please set a different default first." ) context["disable_delete"] = disable_delete context["error_message"] = error_message return context def post(self, request, *args, **kwargs): self.object = self.get_object() user = self.object.user # Check if the friend code is the only one; prevent deletion. if user.friend_codes.count() == 1: messages.error(request, "Cannot remove your only friend code.") return redirect(self.success_url) # Check if the friend code is set as default; prevent deletion. if user.default_friend_code == self.object: messages.error( request, "Cannot delete your default friend code. Please set a different default first." ) return redirect(self.success_url) # Use the unfiltered manager and filter by the friend code's primary key trade_offer_exists = TradeOffer.all_offers.filter(initiated_by_id=self.object.pk).exists() trade_acceptance_exists = TradeAcceptance.objects.filter(accepted_by_id=self.object.pk).exists() if trade_offer_exists or trade_acceptance_exists: messages.error( request, "Cannot remove this friend code because there are existing trade offers associated with it." ) return redirect(self.success_url) # Proceed to safe deletion. self.object.delete() messages.success(request, "Friend code removed successfully.") return redirect(self.success_url) class ChangeDefaultFriendCodeView(LoginRequiredMixin, View): """ Change the default friend code for the current user. """ def post(self, request, *args, **kwargs): friend_code_id = kwargs.get("pk") friend_code = get_object_or_404(FriendCode, pk=friend_code_id, user=request.user) request.user.set_default_friend_code(friend_code) messages.success(request, "Default friend code updated successfully.") return redirect("list_friend_codes") class EditFriendCodeView(LoginRequiredMixin, UpdateView): """ Edit the in-game name for a friend code. The friend code itself is displayed as plain text. Also includes "Set Default" and "Delete" buttons in the template. """ model = FriendCode # Only the in_game_name field is editable fields = ['in_game_name'] template_name = "friend_codes/edit_friend_code.html" context_object_name = "friend_code" success_url = reverse_lazy("list_friend_codes") def get_queryset(self): # Ensure the user can only edit their own friend codes return FriendCode.objects.filter(user=self.request.user) def form_valid(self, form): messages.success(self.request, "Friend code updated successfully.") return super().form_valid(form) class DashboardView(LoginRequiredMixin, FriendCodeRequiredMixin, TemplateView): template_name = "account/dashboard.html" def post(self, request, *args, **kwargs): if 'update_settings' in request.POST: from accounts.forms import UserSettingsForm form = UserSettingsForm(request.POST, instance=request.user) if form.is_valid(): form.save() messages.success(request, "Settings updated successfully.") else: messages.error(request, "Please correct the errors below.") return self.get(request, *args, **kwargs) def get_selected_friend_code(self): friend_codes = self.request.user.friend_codes.all() friend_code_param = self.request.GET.get("friend_code") if friend_code_param: try: selected_friend_code = friend_codes.get(pk=friend_code_param) except friend_codes.model.DoesNotExist: selected_friend_code = self.request.user.default_friend_code or friend_codes.first() else: selected_friend_code = self.request.user.default_friend_code or friend_codes.first() if not selected_friend_code: raise PermissionDenied("You do not have an active friend code associated with your account.") return selected_friend_code def get_dashboard_offers_paginated(self, page_param): from django.core.paginator import Paginator selected_friend_code = self.get_selected_friend_code() queryset = TradeOffer.objects.filter(initiated_by=selected_friend_code, is_closed=False) return Paginator(queryset, 10).get_page(page_param) def get_involved_acceptances(self, selected_friend_code): from django.db.models import Q terminal_states = [ TradeAcceptance.AcceptanceState.THANKED_BY_INITIATOR, TradeAcceptance.AcceptanceState.THANKED_BY_ACCEPTOR, TradeAcceptance.AcceptanceState.THANKED_BY_BOTH, TradeAcceptance.AcceptanceState.REJECTED_BY_INITIATOR, TradeAcceptance.AcceptanceState.REJECTED_BY_ACCEPTOR, ] involved = TradeAcceptance.objects.filter( Q(trade_offer__initiated_by=selected_friend_code) | Q(accepted_by=selected_friend_code) ).order_by("-updated_at") return involved.exclude(state__in=terminal_states) def get_trade_acceptances_waiting_paginated(self, page_param): selected_friend_code = self.get_selected_friend_code() involved = self.get_involved_acceptances(selected_friend_code) from django.db.models import Q waiting = involved.filter( Q(trade_offer__initiated_by=selected_friend_code, state__in=[ TradeAcceptance.AcceptanceState.ACCEPTED, TradeAcceptance.AcceptanceState.RECEIVED, ]) | Q(accepted_by=selected_friend_code, state__in=[TradeAcceptance.AcceptanceState.SENT]) ) from django.core.paginator import Paginator return Paginator(waiting, 10).get_page(page_param) def get_other_party_trade_acceptances_paginated(self, page_param): selected_friend_code = self.get_selected_friend_code() involved = self.get_involved_acceptances(selected_friend_code) from django.db.models import Q waiting = involved.filter( Q(trade_offer__initiated_by=selected_friend_code, state__in=[ TradeAcceptance.AcceptanceState.ACCEPTED, TradeAcceptance.AcceptanceState.RECEIVED, ]) | Q(accepted_by=selected_friend_code, state__in=[TradeAcceptance.AcceptanceState.SENT]) ) others = involved.exclude(pk__in=waiting.values("pk")) from django.core.paginator import Paginator return Paginator(others, 10).get_page(page_param) def get_closed_offers_paginated(self, page_param): from django.core.paginator import Paginator selected_friend_code = self.get_selected_friend_code() queryset = TradeOffer.objects.filter(initiated_by=selected_friend_code, is_closed=True) return Paginator(queryset, 10).get_page(page_param) def get_closed_acceptances_paginated(self, page_param): from django.db.models import Q from django.core.paginator import Paginator selected_friend_code = self.get_selected_friend_code() terminal_success_states = [ TradeAcceptance.AcceptanceState.THANKED_BY_INITIATOR, TradeAcceptance.AcceptanceState.THANKED_BY_ACCEPTOR, TradeAcceptance.AcceptanceState.THANKED_BY_BOTH, ] acceptance_qs = TradeAcceptance.objects.filter( Q(trade_offer__initiated_by=selected_friend_code) | Q(accepted_by=selected_friend_code), state__in=terminal_success_states ).order_by("-updated_at") return Paginator(acceptance_qs, 10).get_page(page_param) def get_rejected_by_me_paginated(self, page_param): from django.db.models import Q from django.core.paginator import Paginator selected_friend_code = self.get_selected_friend_code() rejection = TradeAcceptance.objects.filter( Q(trade_offer__initiated_by=selected_friend_code, state=TradeAcceptance.AcceptanceState.REJECTED_BY_INITIATOR) | Q(accepted_by=selected_friend_code, state=TradeAcceptance.AcceptanceState.REJECTED_BY_ACCEPTOR) ).order_by("-updated_at") return Paginator(rejection, 10).get_page(page_param) def get_rejected_by_them_paginated(self, page_param): from django.db.models import Q from django.core.paginator import Paginator selected_friend_code = self.get_selected_friend_code() rejection = TradeAcceptance.objects.filter( Q(trade_offer__initiated_by=selected_friend_code, state=TradeAcceptance.AcceptanceState.REJECTED_BY_ACCEPTOR) | Q(accepted_by=selected_friend_code, state=TradeAcceptance.AcceptanceState.REJECTED_BY_INITIATOR) ).order_by("-updated_at") return Paginator(rejection, 10).get_page(page_param) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) request = self.request selected_friend_code = self.get_selected_friend_code() context["selected_friend_code"] = selected_friend_code context["friend_codes"] = request.user.friend_codes.all() offers_page = request.GET.get("offers_page", 1) waiting_page = request.GET.get("waiting_page", 1) other_page = request.GET.get("other_page", 1) closed_offers_page = request.GET.get("closed_offers_page", 1) closed_acceptances_page = request.GET.get("closed_acceptances_page", 1) rejected_by_me_page = request.GET.get("rejected_by_me_page", 1) rejected_by_them_page = request.GET.get("rejected_by_them_page", 1) context["dashboard_offers_paginated"] = self.get_dashboard_offers_paginated(offers_page) context["trade_acceptances_waiting_paginated"] = self.get_trade_acceptances_waiting_paginated(waiting_page) context["other_party_trade_acceptances_paginated"] = self.get_other_party_trade_acceptances_paginated(other_page) context["closed_offers_paginated"] = self.get_closed_offers_paginated(closed_offers_page) context["closed_acceptances_paginated"] = self.get_closed_acceptances_paginated(closed_acceptances_page) context["rejected_by_me_paginated"] = self.get_rejected_by_me_paginated(rejected_by_me_page) context["rejected_by_them_paginated"] = self.get_rejected_by_them_paginated(rejected_by_them_page) from accounts.forms import UserSettingsForm context["settings_form"] = UserSettingsForm(instance=request.user) context["active_tab"] = request.GET.get("tab", "dash") return context