574 lines
26 KiB
Python
574 lines
26 KiB
Python
from django.views.generic import TemplateView, DeleteView, CreateView, ListView, DetailView, UpdateView, FormView
|
|
from django.urls import reverse_lazy
|
|
from django.http import HttpResponseRedirect, JsonResponse
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.shortcuts import get_object_or_404, render
|
|
from django.core.exceptions import PermissionDenied, ValidationError
|
|
from django.views.generic.edit import FormMixin
|
|
from django.utils import timezone
|
|
from django.db.models import Q, Case, When, Value, BooleanField, Prefetch, F
|
|
from django.utils.decorators import method_decorator
|
|
from django.views.decorators.http import require_http_methods
|
|
from django.core.paginator import Paginator
|
|
from django.contrib import messages
|
|
|
|
from .models import TradeOffer, TradeAcceptance
|
|
from .forms import (TradeOfferAcceptForm,
|
|
TradeAcceptanceCreateForm, TradeOfferCreateForm, TradeAcceptanceTransitionForm)
|
|
from cards.models import Card
|
|
|
|
class TradeOfferCreateView(LoginRequiredMixin, CreateView):
|
|
model = TradeOffer
|
|
form_class = TradeOfferCreateForm
|
|
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.
|
|
form.fields["initiated_by"].queryset = self.request.user.friend_codes.all()
|
|
return form
|
|
|
|
def get_initial(self):
|
|
initial = super().get_initial()
|
|
initial["have_cards"] = self.request.GET.getlist("have_cards")
|
|
initial["want_cards"] = self.request.GET.getlist("want_cards")
|
|
if self.request.user.friend_codes.count() == 1:
|
|
initial["initiated_by"] = self.request.user.friend_codes.first().pk
|
|
return initial
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
from cards.models import Card
|
|
# Ensure available_cards is a proper QuerySet
|
|
context["cards"] = Card.objects.all().order_by("name", "rarity__pk") \
|
|
.select_related("rarity", "cardset") \
|
|
.prefetch_related("decks")
|
|
friend_codes = self.request.user.friend_codes.all()
|
|
if "initiated_by" in self.request.GET:
|
|
try:
|
|
selected_friend_code = friend_codes.get(pk=self.request.GET.get("initiated_by"))
|
|
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()
|
|
context["friend_codes"] = friend_codes
|
|
context["selected_friend_code"] = selected_friend_code
|
|
return context
|
|
|
|
def form_valid(self, form):
|
|
friend_codes = self.request.user.friend_codes.all()
|
|
if form.cleaned_data.get("initiated_by") not in friend_codes:
|
|
raise PermissionDenied("You cannot initiate trade offers for friend codes that do not belong to you.")
|
|
self.object = form.save()
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
|
|
class TradeOfferAllListView(ListView):
|
|
model = TradeOffer
|
|
template_name = "trades/trade_offer_all_list.html"
|
|
|
|
def get_context_data(self, *, object_list=None, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
request = self.request
|
|
show_closed = request.GET.get("show_closed", "false").lower() == "true"
|
|
context["show_closed"] = show_closed
|
|
|
|
# Build the queryset with our related objects.
|
|
queryset = (
|
|
TradeOffer.objects.select_related('initiated_by')
|
|
.prefetch_related(
|
|
'trade_offer_have_cards__card',
|
|
'trade_offer_want_cards__card',
|
|
Prefetch(
|
|
'acceptances',
|
|
queryset=TradeAcceptance.objects.select_related('accepted_by', 'requested_card', 'offered_card')
|
|
)
|
|
)
|
|
.order_by("-updated_at")
|
|
)
|
|
if show_closed:
|
|
queryset = queryset.filter(is_closed=True)
|
|
else:
|
|
queryset = queryset.filter(is_closed=False)
|
|
|
|
# On initial load, use the 'offers_page' parameter.
|
|
offers_page = request.GET.get("offers_page")
|
|
offers_paginator = Paginator(queryset, 10)
|
|
context["all_trade_offers_paginated"] = offers_paginator.get_page(offers_page)
|
|
return context
|
|
|
|
def render_to_response(self, context, **response_kwargs):
|
|
# For AJAX requests, return only the paginated fragment.
|
|
if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
|
page = self.request.GET.get("page")
|
|
show_closed = self.request.GET.get("show_closed", "false").lower() == "true"
|
|
|
|
queryset = (
|
|
TradeOffer.objects.select_related('initiated_by')
|
|
.prefetch_related(
|
|
'trade_offer_have_cards__card',
|
|
'trade_offer_want_cards__card',
|
|
Prefetch(
|
|
'acceptances',
|
|
queryset=TradeAcceptance.objects.select_related('accepted_by', 'requested_card', 'offered_card')
|
|
)
|
|
)
|
|
.order_by("-updated_at")
|
|
)
|
|
if show_closed:
|
|
queryset = queryset.filter(is_closed=True)
|
|
else:
|
|
queryset = queryset.filter(is_closed=False)
|
|
paginated_offers = Paginator(queryset, 10).get_page(page)
|
|
return render(
|
|
self.request,
|
|
"trades/_trade_offer_list_paginated.html",
|
|
{"offers": paginated_offers}
|
|
)
|
|
return super().render_to_response(context, **response_kwargs)
|
|
|
|
class TradeOfferMyListView(LoginRequiredMixin, ListView):
|
|
model = TradeOffer # Fallback model; our context data holds separate filtered querysets.
|
|
template_name = "trades/trade_offer_my_list.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
if request.user.is_authenticated and 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')
|
|
.prefetch_related(
|
|
'trade_offer_have_cards__card',
|
|
'trade_offer_want_cards__card',
|
|
Prefetch(
|
|
'acceptances',
|
|
queryset=TradeAcceptance.objects.select_related('accepted_by', 'requested_card', 'offered_card')
|
|
)
|
|
)
|
|
.order_by("-updated_at")
|
|
)
|
|
|
|
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_show_closed(self):
|
|
return self.request.GET.get("show_closed", "false").lower() == "true"
|
|
|
|
def get_my_trade_offers_paginated(self, page_param):
|
|
selected_friend_code = self.get_selected_friend_code()
|
|
queryset = self.get_queryset().filter(initiated_by=selected_friend_code)
|
|
if self.get_show_closed():
|
|
queryset = queryset.filter(is_closed=True)
|
|
else:
|
|
queryset = queryset.filter(is_closed=False)
|
|
return Paginator(queryset, 10).get_page(page_param)
|
|
|
|
def get_involved_acceptances(self, selected_friend_code):
|
|
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_acceptances_qs = TradeAcceptance.objects.filter(
|
|
Q(trade_offer__initiated_by=selected_friend_code) | Q(accepted_by=selected_friend_code)
|
|
).order_by("-updated_at")
|
|
if self.get_show_closed():
|
|
return involved_acceptances_qs.filter(state__in=terminal_states)
|
|
return involved_acceptances_qs.exclude(state__in=terminal_states)
|
|
|
|
def get_trade_acceptances_waiting_paginated(self, page_param):
|
|
selected_friend_code = self.get_selected_friend_code()
|
|
involved_acceptances = self.get_involved_acceptances(selected_friend_code)
|
|
waiting_acceptances = involved_acceptances.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])
|
|
)
|
|
return Paginator(waiting_acceptances, 10).get_page(page_param)
|
|
|
|
def get_other_party_trade_acceptances_paginated(self, page_param):
|
|
selected_friend_code = self.get_selected_friend_code()
|
|
involved_acceptances = self.get_involved_acceptances(selected_friend_code)
|
|
waiting_acceptances = involved_acceptances.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])
|
|
)
|
|
other_acceptances = involved_acceptances.exclude(pk__in=waiting_acceptances.values("pk"))
|
|
return Paginator(other_acceptances, 10).get_page(page_param)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
request = self.request
|
|
show_closed = self.get_show_closed()
|
|
context["show_closed"] = show_closed
|
|
|
|
selected_friend_code = self.get_selected_friend_code()
|
|
context["selected_friend_code"] = selected_friend_code
|
|
context["friend_codes"] = request.user.friend_codes.all()
|
|
|
|
# Use request params for initial full page load (could be None)
|
|
offers_page = request.GET.get("offers_page")
|
|
waiting_page = request.GET.get("waiting_page")
|
|
other_page = request.GET.get("other_page")
|
|
context["my_trade_offers_paginated"] = self.get_my_trade_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)
|
|
|
|
return context
|
|
|
|
def render_to_response(self, context, **response_kwargs):
|
|
# For AJAX requests, return only the paginated fragment.
|
|
if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
|
ajax_section = self.request.GET.get("ajax_section")
|
|
page = self.request.GET.get("page")
|
|
if ajax_section == "my_trade_offers":
|
|
offers = self.get_my_trade_offers_paginated(page)
|
|
elif ajax_section == "waiting_acceptances":
|
|
offers = self.get_trade_acceptances_waiting_paginated(page)
|
|
elif ajax_section == "other_party_acceptances":
|
|
offers = self.get_other_party_trade_acceptances_paginated(page)
|
|
else:
|
|
# Fallback to my trade offers.
|
|
offers = self.get_my_trade_offers_paginated(page)
|
|
return render(self.request, "trades/_trade_offer_list_paginated.html", {"offers": offers})
|
|
return super().render_to_response(context, **response_kwargs)
|
|
|
|
class TradeOfferDeleteView(LoginRequiredMixin, DeleteView):
|
|
model = TradeOffer
|
|
success_url = reverse_lazy("trade_offer_list")
|
|
template_name = "trades/trade_offer_delete.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
trade_offer = self.get_object()
|
|
if trade_offer.initiated_by_id not in request.user.friend_codes.values_list("id", flat=True):
|
|
raise PermissionDenied("You are not authorized to delete or close this trade offer.")
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
trade_offer = self.get_object()
|
|
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,
|
|
]
|
|
active_acceptances = trade_offer.acceptances.exclude(state__in=terminal_states)
|
|
if trade_offer.acceptances.count() == 0:
|
|
context["action"] = "delete"
|
|
elif trade_offer.acceptances.count() > 0 and not active_acceptances.exists():
|
|
context["action"] = "close"
|
|
else:
|
|
context["action"] = None
|
|
return context
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
trade_offer = self.get_object()
|
|
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,
|
|
]
|
|
active_acceptances = trade_offer.acceptances.exclude(state__in=terminal_states)
|
|
if active_acceptances.exists():
|
|
messages.error(request, "Cannot delete or close this trade offer because there are active acceptances.")
|
|
context = self.get_context_data(object=trade_offer)
|
|
return self.render_to_response(context)
|
|
else:
|
|
if trade_offer.acceptances.count() > 0:
|
|
trade_offer.is_closed = True
|
|
trade_offer.save(update_fields=["is_closed"])
|
|
messages.success(request, "Trade offer has been marked as closed.")
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
else:
|
|
messages.success(request, "Trade offer has been deleted.")
|
|
return super().delete(request, *args, **kwargs)
|
|
|
|
class TradeOfferSearchView(LoginRequiredMixin, ListView):
|
|
"""
|
|
Reworked trade offer search view using POST.
|
|
|
|
This view allows users to search active trade offers based on the cards they have and/or want.
|
|
The POST parameters (offered_cards and wanted_cards) are expected to be in the format 'card_id:quantity'.
|
|
If both types of selections are provided, the resultant queryset must satisfy both conditions.
|
|
Offers initiated by any of the user's friend codes are excluded.
|
|
|
|
When the request is AJAX (via X-Requested-With header), only the search results fragment
|
|
(_search_results.html) is rendered. On GET (initial page load), the search results queryset
|
|
is empty.
|
|
"""
|
|
model = TradeOffer
|
|
context_object_name = "search_results"
|
|
template_name = "trades/trade_offer_search.html"
|
|
paginate_by = 10
|
|
http_method_names = ["get", "post"]
|
|
|
|
def parse_selections(self, selection_list):
|
|
"""
|
|
Parse a list of selections (each formatted as 'card_id:quantity') into a list of tuples.
|
|
Defaults the quantity to 1 if missing.
|
|
"""
|
|
results = []
|
|
for item in selection_list:
|
|
parts = item.split(":")
|
|
try:
|
|
card_id = int(parts[0])
|
|
except ValueError:
|
|
continue # Skip invalid values.
|
|
qty = 1
|
|
if len(parts) > 1:
|
|
try:
|
|
qty = int(parts[1])
|
|
except ValueError:
|
|
qty = 1
|
|
results.append((card_id, qty))
|
|
return results
|
|
|
|
def get_queryset(self):
|
|
from django.db.models import F
|
|
# For a GET request (initial load), return an empty queryset.
|
|
if self.request.method == "GET":
|
|
return TradeOffer.objects.none()
|
|
|
|
# Parse the POST data for offered and wanted selections.
|
|
offered_selections = self.parse_selections(self.request.POST.getlist("offered_cards"))
|
|
wanted_selections = self.parse_selections(self.request.POST.getlist("wanted_cards"))
|
|
|
|
# If no selections are provided, return an empty queryset.
|
|
if not offered_selections and not wanted_selections:
|
|
return TradeOffer.objects.none()
|
|
|
|
qs = TradeOffer.objects.filter(
|
|
is_closed=False,
|
|
).exclude(initiated_by__in=self.request.user.friend_codes.all())
|
|
|
|
# Chain filters for offered selections (i.e. the user "has" cards).
|
|
if offered_selections:
|
|
for card_id, qty in offered_selections:
|
|
qs = qs.filter(
|
|
trade_offer_want_cards__card_id=card_id,
|
|
trade_offer_want_cards__quantity__gte=qty,
|
|
)
|
|
|
|
# Chain filters for wanted selections (i.e. the user "wants" cards).
|
|
if wanted_selections:
|
|
for card_id, qty in wanted_selections:
|
|
qs = qs.filter(
|
|
trade_offer_have_cards__card_id=card_id,
|
|
trade_offer_have_cards__quantity__gte=qty,
|
|
)
|
|
|
|
return qs.distinct()
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
# For POST, simply process the search through get().
|
|
return self.get(request, *args, **kwargs)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
from cards.models import Card
|
|
# Populate available_cards to re-populate the multiselects.
|
|
context["cards"] = Card.objects.all().order_by("name", "rarity__pk") \
|
|
.select_related("rarity", "cardset")
|
|
if self.request.method == "POST":
|
|
context["offered_cards"] = self.request.POST.getlist("offered_cards")
|
|
context["wanted_cards"] = self.request.POST.getlist("wanted_cards")
|
|
else:
|
|
context["offered_cards"] = []
|
|
context["wanted_cards"] = []
|
|
return context
|
|
|
|
def render_to_response(self, context, **response_kwargs):
|
|
"""
|
|
Render the AJAX fragment if the request is AJAX; otherwise, render the complete page.
|
|
"""
|
|
if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
|
from django.shortcuts import render
|
|
return render(self.request, "trades/_search_results.html", context)
|
|
else:
|
|
return super().render_to_response(context, **response_kwargs)
|
|
|
|
class TradeOfferDetailView(LoginRequiredMixin, DetailView):
|
|
"""
|
|
Displays the details of a TradeOffer along with its active acceptances.
|
|
If the offer is still open and the current user is not its initiator,
|
|
an acceptance form is provided to create a new acceptance.
|
|
"""
|
|
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')
|
|
.prefetch_related(
|
|
'trade_offer_have_cards__card',
|
|
'trade_offer_want_cards__card',
|
|
Prefetch(
|
|
'acceptances',
|
|
queryset=TradeAcceptance.objects.select_related(
|
|
'accepted_by', 'requested_card', 'offered_card'
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
trade_offer = self.get_object()
|
|
|
|
# Define terminal (closed) acceptance states based on our new system:
|
|
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,
|
|
]
|
|
|
|
# For example, if you want to separate active from terminal acceptances:
|
|
context["acceptances"] = trade_offer.acceptances.all()
|
|
|
|
# Option 1: Filter active acceptances using the queryset lookup.
|
|
context["active_acceptances"] = trade_offer.acceptances.exclude(state__in=terminal_states)
|
|
|
|
|
|
user_friend_codes = self.request.user.friend_codes.all()
|
|
|
|
# Add context flag and deletion URL if the current user is the initiator
|
|
if trade_offer.initiated_by in user_friend_codes:
|
|
context["is_initiator"] = True
|
|
context["delete_close_url"] = reverse_lazy("trade_offer_delete", kwargs={"pk": trade_offer.pk})
|
|
else:
|
|
context["is_initiator"] = False
|
|
|
|
# If the current user is not the initiator and the offer is open, allow a new acceptance.
|
|
if trade_offer.initiated_by not in user_friend_codes and not trade_offer.is_closed:
|
|
context["acceptance_form"] = TradeAcceptanceCreateForm(
|
|
trade_offer=trade_offer, friend_codes=user_friend_codes
|
|
)
|
|
return context
|
|
|
|
class TradeAcceptanceCreateView(LoginRequiredMixin, CreateView):
|
|
"""
|
|
View to create a new TradeAcceptance.
|
|
The URL should provide 'offer_pk' so that the proper TradeOffer can be identified.
|
|
"""
|
|
model = TradeAcceptance
|
|
form_class = TradeAcceptanceCreateForm
|
|
template_name = "trades/trade_acceptance_create.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
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 self.trade_offer.is_closed:
|
|
raise PermissionDenied("You cannot accept this trade offer.")
|
|
if not request.user.friend_codes.exists():
|
|
raise PermissionDenied("No friend codes available for your account.")
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_trade_offer(self):
|
|
return (
|
|
TradeOffer.objects.select_related('initiated_by')
|
|
.prefetch_related(
|
|
'trade_offer_want_cards__card',
|
|
'trade_offer_have_cards__card',
|
|
Prefetch(
|
|
'acceptances',
|
|
queryset=TradeAcceptance.objects.select_related(
|
|
'accepted_by', 'requested_card', 'offered_card'
|
|
)
|
|
)
|
|
)
|
|
.get(pk=self.kwargs['offer_pk'])
|
|
)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['trade_offer'] = self.trade_offer
|
|
kwargs['friend_codes'] = self.request.user.friend_codes.all()
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
form.instance.trade_offer = self.trade_offer
|
|
self.object = form.save()
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("trade_offer_detail", kwargs={"pk": self.trade_offer.pk})
|
|
|
|
class TradeAcceptanceUpdateView(LoginRequiredMixin, UpdateView):
|
|
"""
|
|
View to update the state of an existing TradeAcceptance.
|
|
The allowed state transitions are provided via the form.
|
|
"""
|
|
model = TradeAcceptance
|
|
form_class = TradeAcceptanceTransitionForm
|
|
template_name = "trades/trade_acceptance_update.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
if not request.user.friend_codes.exists():
|
|
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)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
# Pass the current instance to the form so it can set proper allowed transitions.
|
|
kwargs["instance"] = self.object
|
|
kwargs["user"] = self.request.user
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
new_state = form.cleaned_data["state"]
|
|
#match the new state to the TradeAcceptance.AcceptanceState enum
|
|
if new_state not in TradeAcceptance.AcceptanceState:
|
|
form.add_error("state", "Invalid state transition.")
|
|
return self.form_invalid(form)
|
|
try:
|
|
# pass the new state and the current user to the update_state method
|
|
form.instance.update_state(new_state, self.request.user)
|
|
except ValueError as e:
|
|
form.add_error("state", str(e))
|
|
return self.form_invalid(form)
|
|
return HttpResponseRedirect(self.get_success_url())
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("trade_offer_detail", kwargs={"pk": self.object.trade_offer.pk})
|
|
|