From 6295febb4e3547fdaf1fbac34a05f252743e5154 Mon Sep 17 00:00:00 2001 From: Avi Upadhyayula <69180850+aviupadhyayula@users.noreply.github.com> Date: Sun, 13 Oct 2024 21:24:35 -0400 Subject: [PATCH] Add checks for elapsed events in ticketing (#734) * Add check on event end time in cart endpoint * Add tests * Tweak frontend toast * Reinsert comment * style * Remove extra query * Add check to add_to_cart * Add test to add_to_cart * Minor fix * Minor fix * Disable "Get Tickets" button for elapsed events --- backend/clubs/views.py | 38 ++++++++++++-- backend/tests/clubs/test_ticketing.py | 55 +++++++++++++++++++++ frontend/components/Tickets/CartTickets.tsx | 2 +- frontend/pages/events/[id].tsx | 6 ++- 4 files changed, 93 insertions(+), 8 deletions(-) diff --git a/backend/clubs/views.py b/backend/clubs/views.py index f7a93f6d4..f901b339a 100644 --- a/backend/clubs/views.py +++ b/backend/clubs/views.py @@ -2418,10 +2418,17 @@ def add_to_cart(self, request, *args, **kwargs): event = self.get_object() cart, _ = Cart.objects.get_or_create(owner=self.request.user) + # Check if the event has already ended + if event.end_time < timezone.now(): + return Response( + {"detail": "This event has already ended", "success": False}, + status=status.HTTP_403_FORBIDDEN, + ) + # Cannot add tickets that haven't dropped yet if event.ticket_drop_time and timezone.now() < event.ticket_drop_time: return Response( - {"detail": "Ticket drop time has not yet elapsed"}, + {"detail": "Ticket drop time has not yet elapsed", "success": False}, status=status.HTTP_403_FORBIDDEN, ) @@ -2465,7 +2472,10 @@ def add_to_cart(self, request, *args, **kwargs): if tickets.count() < count: return Response( - {"detail": f"Not enough tickets of type {type} left!"}, + { + "detail": f"Not enough tickets of type {type} left!", + "success": False, + }, status=status.HTTP_403_FORBIDDEN, ) cart.tickets.add(*tickets[:count]) @@ -5141,8 +5151,12 @@ def cart(self, request, *args, **kwargs): owner=self.request.user ) + now = timezone.now() + tickets_to_replace = cart.tickets.filter( - Q(owner__isnull=False) | Q(holder__isnull=False) + Q(owner__isnull=False) + | Q(holder__isnull=False) + | Q(event__end_time__lt=now) ).exclude(holder=self.request.user) # In most cases, we won't need to replace, so exit early @@ -5154,16 +5168,30 @@ def cart(self, request, *args, **kwargs): }, ) - # Attempt to replace all tickets that have gone stale + # Attempt to replace all tickets that have gone stale or are for elapsed events replacement_tickets, sold_out_tickets = [], [] tickets_in_cart = cart.tickets.values_list("id", flat=True) tickets_to_replace = tickets_to_replace.select_related("event") for ticket_class in tickets_to_replace.values( - "type", "event", "event__name" + "type", "event", "event__name", "event__end_time" ).annotate(count=Count("id")): # we don't need to lock, since we aren't updating holder/owner + if ticket_class["event__end_time"] < now: + # Event has elapsed, mark all tickets as sold out + sold_out_tickets.append( + { + "type": ticket_class["type"], + "event": { + "id": ticket_class["event"], + "name": ticket_class["event__name"], + }, + "count": ticket_class["count"], + } + ) + continue + available_tickets = Ticket.objects.filter( event=ticket_class["event"], type=ticket_class["type"], diff --git a/backend/tests/clubs/test_ticketing.py b/backend/tests/clubs/test_ticketing.py index e1572ae4d..f1dbce0d6 100644 --- a/backend/tests/clubs/test_ticketing.py +++ b/backend/tests/clubs/test_ticketing.py @@ -496,6 +496,27 @@ def test_add_to_cart(self): self.assertEqual(cart.tickets.filter(type="normal").count(), 2, cart.tickets) self.assertEqual(cart.tickets.filter(type="premium").count(), 1, cart.tickets) + def test_add_to_cart_elapsed_event(self): + self.client.login(username=self.user1.username, password="test") + + # Set the event end time to the past + self.event1.end_time = timezone.now() - timezone.timedelta(days=1) + self.event1.save() + + tickets_to_add = { + "quantities": [ + {"type": "normal", "count": 1}, + ] + } + resp = self.client.post( + reverse("club-events-add-to-cart", args=(self.club1.code, self.event1.pk)), + tickets_to_add, + format="json", + ) + + self.assertEqual(resp.status_code, 403, resp.content) + self.assertIn("This event has already ended", resp.data["detail"], resp.data) + def test_add_to_cart_twice_accumulates(self): self.client.login(username=self.user1.username, password="test") @@ -948,6 +969,40 @@ def test_get_cart_replacement_required_sold_out(self): to_add = set(map(lambda t: str(t.id), tickets_to_add)) self.assertEqual(len(in_cart & to_add), 0, in_cart | to_add) + def test_get_cart_elapsed_event(self): + self.client.login(username=self.user1.username, password="test") + + # Add a few tickets + cart, _ = Cart.objects.get_or_create(owner=self.user1) + tickets_to_add = self.tickets1[:5] + for ticket in tickets_to_add: + cart.tickets.add(ticket) + cart.save() + + # Set the event end time to the past + self.event1.end_time = timezone.now() - timezone.timedelta(days=1) + self.event1.save() + + resp = self.client.get(reverse("tickets-cart"), format="json") + data = resp.json() + + # The cart should now be empty + self.assertEqual(len(data["tickets"]), 0, data) + + # All tickets should be in the sold out array + self.assertEqual(len(data["sold_out"]), 1, data) + + expected_sold_out = { + "type": self.tickets1[0].type, + "event": { + "id": self.event1.id, + "name": self.event1.name, + }, + "count": 5, + } + for key, val in expected_sold_out.items(): + self.assertEqual(data["sold_out"][0][key], val, data) + def test_place_hold_on_tickets(self): from clubs.views import TicketViewSet diff --git a/frontend/components/Tickets/CartTickets.tsx b/frontend/components/Tickets/CartTickets.tsx index 5020ad192..49d8f640b 100644 --- a/frontend/components/Tickets/CartTickets.tsx +++ b/frontend/components/Tickets/CartTickets.tsx @@ -213,7 +213,7 @@ const CartTickets: React.FC = ({ tickets, soldOut }) => { .forEach( (ticket) => { toast.error( - `${ticket.event.name} - ${ticket.type} is sold out and ${ticket.count} ticket${ticket.count && ticket.count > 1 ? 's have' : ' has'} been removed from your cart.`, + `${ticket.event.name} - ${ticket.type} is no longer available and ${ticket.count} ticket${ticket.count && ticket.count > 1 ? 's have' : ' has'} been removed from your cart.`, { style: { color: WHITE }, autoClose: false, diff --git a/frontend/pages/events/[id].tsx b/frontend/pages/events/[id].tsx index 973b979eb..49469c1aa 100644 --- a/frontend/pages/events/[id].tsx +++ b/frontend/pages/events/[id].tsx @@ -362,10 +362,12 @@ const EventPage: React.FC = ({ ))} )}