Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checks for elapsed events in ticketing #734

Merged
merged 11 commits into from
Oct 14, 2024
38 changes: 33 additions & 5 deletions backend/clubs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2416,10 +2416,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,
)

Expand Down Expand Up @@ -2463,7 +2470,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])
Expand Down Expand Up @@ -5139,8 +5149,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
Expand All @@ -5152,16 +5166,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"],
Expand Down
55 changes: 55 additions & 0 deletions backend/tests/clubs/test_ticketing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion frontend/components/Tickets/CartTickets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ const CartTickets: React.FC<CartTicketsProps> = ({ 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,
Expand Down
6 changes: 4 additions & 2 deletions frontend/pages/events/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -361,10 +361,12 @@ const EventPage: React.FC<EventPageProps> = ({
))}
<button
className="button is-primary is-fullwidth mt-4"
disabled={totalAvailableTickets === 0}
disabled={
totalAvailableTickets === 0 || endTime < DateTime.now()
}
onClick={() => setShowTicketModal(true)}
>
Get Tickets
{endTime < DateTime.now() ? 'Event Ended' : 'Get Tickets'}
</button>
</Card>
)}
Expand Down
Loading