Skip to content

Commit

Permalink
Merge branch 'master' into club-approval-response-templates
Browse files Browse the repository at this point in the history
  • Loading branch information
rm03 authored Oct 15, 2024
2 parents a00c4c8 + ca77e21 commit 2c0b4cd
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 15 deletions.
38 changes: 33 additions & 5 deletions backend/clubs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2420,10 +2420,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 @@ -2467,7 +2474,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 @@ -5143,8 +5153,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 @@ -5156,16 +5170,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
4 changes: 2 additions & 2 deletions frontend/components/DisplayButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ const DisplayButtons = ({
<Link href="/create" className="button is-small is-primary">
<Icon
name="plus"
alt={`create ${OBJECT_NAME_SINGULAR}`}
alt={`register ${OBJECT_NAME_SINGULAR}`}
style={iconStylesDark}
/>
Add {OBJECT_NAME_TITLE_SINGULAR}
Register {OBJECT_NAME_TITLE_SINGULAR}
</Link>
)}
</DisplayButtonsTag>
Expand Down
3 changes: 3 additions & 0 deletions frontend/components/Header/Burger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const Burger = ({ toggle }: BurgerProps): ReactElement => (
<a
role="button"
className="navbar-burger burger"
style={{
marginLeft: '8px',
}}
aria-label="menu"
aria-expanded="false"
data-target="navbarBasicExample"
Expand Down
18 changes: 16 additions & 2 deletions frontend/components/Header/Links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ const LoginButton = styled.a`
}
`

export const MobileLoginButton = styled(LoginButton)`
display: none;
margin-right: 0;
${mediaMaxWidth(MD)} {
display: inline-flex;
}
`
const DesktopLoginButton = styled(LoginButton)`
margin-left: 20px;
${mediaMaxWidth(MD)} {
display: none;
}
`

const StyledLinkAnchor = styled.a`
padding: ${LINK_MARGIN} 20px;
color: ${BANNER_TEXT} !important;
Expand Down Expand Up @@ -116,13 +130,13 @@ const Links = ({ userInfo, authenticated, show }: Props): ReactElement => {
FAQ
</StyledLink>
{authenticated === false && (
<LoginButton
<DesktopLoginButton
className="button"
href={`${LOGIN_URL}?next=${router.asPath}`}
onClick={() => logEvent('login', 'click')}
>
Login
</LoginButton>
</DesktopLoginButton>
)}
{authenticated && userInfo && (
<StyledLink href={SETTINGS_ROUTE}>
Expand Down
26 changes: 24 additions & 2 deletions frontend/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { ReactElement, useEffect, useState } from 'react'
import styled from 'styled-components'

import { LOGIN_URL } from '~/utils'
import { logEvent } from '~/utils/analytics'

import { BANNER_BG, BANNER_TEXT, BORDER } from '../../constants/colors'
import {
ANIMATION_DURATION,
Expand Down Expand Up @@ -31,7 +35,7 @@ import {
import Burger from './Burger'
import Feedback from './Feedback'
import Heading from './Head'
import Links from './Links'
import Links, { MobileLoginButton } from './Links'

const Nav = styled.nav`
height: ${NAV_HEIGHT};
Expand Down Expand Up @@ -164,6 +168,7 @@ const isHub = SITE_ID === 'fyh'

const Header = ({ authenticated, userInfo }: HeaderProps): ReactElement => {
const [show, setShow] = useState(false)
const router = useRouter()

const toggle = () => setShow(!show)

Expand All @@ -184,7 +189,24 @@ const Header = ({ authenticated, userInfo }: HeaderProps): ReactElement => {
<Title>{SITE_NAME}</Title>
</LogoItem>
</Link>
<Burger toggle={toggle} />
<div
style={{
flex: 1,
display: 'flex',
justifyContent: 'flex-end',
}}
>
{authenticated === false && (
<MobileLoginButton
className="button"
href={`${LOGIN_URL}?next=${router.asPath}`}
onClick={() => logEvent('login', 'click')}
>
Login
</MobileLoginButton>
)}
<Burger toggle={toggle} />
</div>
</div>
<Links userInfo={userInfo} authenticated={authenticated} show={show} />
</Nav>
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 @@ -213,7 +213,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 @@ -362,10 +362,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
2 changes: 1 addition & 1 deletion k8s/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class MyChart extends PennLabsChart {
});

new CronJob(this, 'daily-notifications', {
schedule: cronTime.everyDayAt(13),
schedule: cronTime.onSpecificDaysAt(['monday', 'wednesday', 'friday'], 10, 0),
image: backendImage,
secret: clubsSecret,
cmd: ['python', 'manage.py', 'daily_notifications'],
Expand Down

0 comments on commit 2c0b4cd

Please sign in to comment.