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 support for ticket drop times #672

Merged
merged 10 commits into from
Apr 23, 2024
1 change: 1 addition & 0 deletions backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ numpy = "*"
inflection = "*"
cybersource-rest-client-python = "*"
pyjwt = "*"
freezegun = "*"
aviupadhyayula marked this conversation as resolved.
Show resolved Hide resolved

[requires]
python_version = "3.11"
87 changes: 49 additions & 38 deletions backend/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions backend/clubs/migrations/0104_event_ticket_drop_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.4 on 2024-04-21 04:35

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("clubs", "0103_ticket_group_discount_ticket_group_size"),
]

operations = [
migrations.AddField(
model_name="event",
name="ticket_drop_time",
field=models.DateTimeField(blank=True, null=True),
),
]
1 change: 1 addition & 0 deletions backend/clubs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,7 @@ class Event(models.Model):
RecurringEvent, on_delete=models.CASCADE, blank=True, null=True
)
ticket_order_limit = models.IntegerField(default=10)
ticket_drop_time = models.DateTimeField(null=True, blank=True)

OTHER = 0
RECRUITMENT = 1
Expand Down
22 changes: 22 additions & 0 deletions backend/clubs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,13 @@ def add_to_cart(self, request, *args, **kwargs):
event = self.get_object()
cart, _ = Cart.objects.get_or_create(owner=self.request.user)

# 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"},
status=status.HTTP_403_FORBIDDEN,
)

quantities = request.data.get("quantities")
if not quantities:
return Response(
Expand Down Expand Up @@ -2618,6 +2625,9 @@ def create_tickets(self, request, *args, **kwargs):
order_limit:
type: int
required: false
delay_drop:
type: boolean
required: false
responses:
"200":
content:
Expand All @@ -2639,6 +2649,13 @@ def create_tickets(self, request, *args, **kwargs):
"""
event = self.get_object()

# Tickets can't be edited after they've dropped
if event.ticket_drop_time and timezone.now() > event.ticket_drop_time:
return Response(
{"detail": "Tickets cannot be edited after they have dropped"},
status=status.HTTP_403_FORBIDDEN,
)

quantities = request.data.get("quantities", [])
if not quantities:
return Response(
Expand Down Expand Up @@ -2707,6 +2724,11 @@ def create_tickets(self, request, *args, **kwargs):
event.ticket_order_limit = order_limit
event.save()

delay_drop = request.data.get("delay_drop", None)
aviupadhyayula marked this conversation as resolved.
Show resolved Hide resolved
if delay_drop is not None:
event.ticket_drop_time = timezone.now() + timezone.timedelta(hours=12)
event.save()

return Response({"detail": "success"})

@action(detail=True, methods=["post"])
Expand Down
59 changes: 59 additions & 0 deletions backend/tests/clubs/test_ticketing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from freezegun import freeze_time
from rest_framework.test import APIClient

from clubs.models import Cart, Club, Event, Ticket, TicketTransactionRecord
Expand Down Expand Up @@ -155,6 +156,43 @@ def test_create_ticket_offerings_bad_data(self):
self.assertIn(resp.status_code, [400], resp.content)
self.assertEqual(Ticket.objects.filter(type__contains="_").count(), 0, data)

def test_create_ticket_offerings_delay_drop(self):
self.client.login(username=self.user1.username, password="test")

args = {
"quantities": [
{"type": "_normal", "count": 20, "price": 10},
{"type": "_premium", "count": 10, "price": 20},
],
"delay_drop": True,
}
_ = self.client.put(
reverse("club-events-tickets", args=(self.club1.code, self.event1.pk)),
args,
format="json",
)

self.event1.refresh_from_db()

# Drop time should be set
self.assertIsNotNone(self.event1.ticket_drop_time)

# Drop time should be 12 hours from initial ticket creation
expected_drop_time = timezone.now() + timezone.timedelta(hours=12)
diff = abs(self.event1.ticket_drop_time - expected_drop_time)
self.assertTrue(diff < timezone.timedelta(minutes=5))

# Move Django's internal clock 13 hours forward
with freeze_time(timezone.now() + timezone.timedelta(hours=13)):
aviupadhyayula marked this conversation as resolved.
Show resolved Hide resolved
resp = self.client.put(
reverse("club-events-tickets", args=(self.club1.code, self.event1.pk)),
args,
format="json",
)

# Tickets shouldn't be editable after drop time has elapsed
self.assertEqual(resp.status_code, 403, resp.content)

def test_get_tickets_information_no_tickets(self):
# Delete all the tickets
Ticket.objects.all().delete()
Expand Down Expand Up @@ -304,6 +342,27 @@ def test_add_to_cart_tickets_unavailable(self):
"Not enough tickets of type normal left!", resp.data["detail"], resp.data
)

def test_add_to_cart_before_ticket_drop(self):
self.client.login(username=self.user1.username, password="test")

# Set drop time
self.event1.ticket_drop_time = timezone.now() + timedelta(hours=12)
self.event1.save()

tickets_to_add = {
"quantities": [
{"type": "normal", "count": 2},
]
}
resp = self.client.post(
reverse("club-events-add-to-cart", args=(self.club1.code, self.event1.pk)),
tickets_to_add,
format="json",
)

# Tickets should not be added to cart before drop time
self.assertEqual(resp.status_code, 403, resp.content)

def test_remove_from_cart(self):
self.client.login(username=self.user1.username, password="test")

Expand Down
Loading