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

Revert "GSR Revamp" #236

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
763 changes: 392 additions & 371 deletions backend/gsr_booking/api_wrapper.py

Large diffs are not rendered by default.

106 changes: 106 additions & 0 deletions backend/gsr_booking/group_logic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import datetime
import random

from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404

from gsr_booking.api_wrapper import APIError, BookingWrapper, CreditType
from gsr_booking.models import GSR, GroupMembership, Reservation


User = get_user_model()


class GroupBook:
def __init__(self):
self.bw = BookingWrapper()

def get_wharton_users(self, group):
"""
Returns list of wharton users of a Group in random ordering
"""
# TODO: filter for pennkey allowed
wharton_users = list(GroupMembership.objects.filter(group=group, is_wharton=True))
# shuffle to prevent sequential booking
random.shuffle(wharton_users)
return wharton_users

def get_all_users(self, group):
"""
Returns list of all users of a Group in random ordering
"""
# TODO: filter for pennkey allowed
all_users = list(GroupMembership.objects.filter(group=group))
# shuffle to prevent sequential booking
random.shuffle(all_users)
return all_users

def book_room(self, gid, rid, room_name, start, end, user, group):
"""
Book function for Group
"""
# TODO: check credits
gsr = get_object_or_404(GSR, gid=gid)

start = datetime.datetime.strptime(start, "%Y-%m-%dT%H:%M:%S%z")
end = datetime.datetime.strptime(end, "%Y-%m-%dT%H:%M:%S%z")

# sets users and credit_id depending on the gsr type
if gsr.kind == GSR.KIND_WHARTON:
users = self.get_wharton_users(group)
credit_id = gsr.lid
else:
users = self.get_all_users(group)
credit_id = CreditType.LIBCAL.value

total_credits = sum([self.bw.check_credits(usr.user).get(credit_id, 0) for usr in users])
duration = int((end.timestamp() - start.timestamp()) / 60)
if total_credits < duration:
raise APIError("Not Enough Credits to Book")

Check warning on line 59 in backend/gsr_booking/group_logic.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/group_logic.py#L59

Added line #L59 was not covered by tests
if duration % 30 != 0:
raise APIError("Invalid duration")

Check warning on line 61 in backend/gsr_booking/group_logic.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/group_logic.py#L61

Added line #L61 was not covered by tests

# creates reservation object to be used to group each booking
reservation = Reservation.objects.create(start=start, end=end, creator=user, group=group)

# we could potentially repeat using a user to make a 30 min booking so
# loop until total duration booked
while duration > 0:
# loop through each user and try to make a 30 min booking
# under that user if they have enough credits
for usr in users:
credit = self.bw.check_credits(usr.user).get(credit_id, 0)
if credit < 30:
continue

Check warning on line 74 in backend/gsr_booking/group_logic.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/group_logic.py#L74

Added line #L74 was not covered by tests
curr_end = start + datetime.timedelta(minutes=30) # end of current booking
booking = self.bw.book_room(
gid,
rid,
room_name,
start.strftime("%Y-%m-%dT%H:%M:%S%z"),
curr_end.strftime("%Y-%m-%dT%H:%M:%S%z"),
usr.user,
group_book=True,
)
booking.reservation = reservation
booking.save()
# update new start and duration appropriately
start = curr_end
duration -= 30
if duration <= 0:
break
return reservation

def get_availability(self, lid, gid, start, end, user, group):
"""
Availability function for Group
"""

gsr = GSR.objects.filter(gid=gid).first()
if gsr.kind == GSR.KIND_WHARTON:

Check warning on line 100 in backend/gsr_booking/group_logic.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/group_logic.py#L99-L100

Added lines #L99 - L100 were not covered by tests
# check if wharton users is non-empty
wharton_user = GroupMembership.objects.filter(group=group, is_wharton=True).first()
if wharton_user:
return self.bw.get_availability(lid, gid, start, end, wharton_user.user)

Check warning on line 104 in backend/gsr_booking/group_logic.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/group_logic.py#L102-L104

Added lines #L102 - L104 were not covered by tests

return self.bw.get_availability(lid, gid, start, end, user)

Check warning on line 106 in backend/gsr_booking/group_logic.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/group_logic.py#L106

Added line #L106 was not covered by tests
24 changes: 0 additions & 24 deletions backend/gsr_booking/migrations/0011_alter_reservation_group.py

This file was deleted.

24 changes: 18 additions & 6 deletions backend/gsr_booking/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import requests
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.utils import timezone
from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout


User = get_user_model()
Expand Down Expand Up @@ -42,7 +45,20 @@
super().save(*args, **kwargs)

def check_wharton(self):
return WhartonGSRBooker.is_wharton(self.user)
# not using api_wrapper.py to prevent circular dependency
url = f"https://apps.wharton.upenn.edu/gsr/api/v1/{self.user.username}/privileges"
try:
response = requests.get(

Check warning on line 51 in backend/gsr_booking/models.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/models.py#L49-L51

Added lines #L49 - L51 were not covered by tests
url, headers={"Authorization": f"Token {settings.WHARTON_TOKEN}"}
)

if response.status_code != 200:
return None

Check warning on line 56 in backend/gsr_booking/models.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/models.py#L55-L56

Added lines #L55 - L56 were not covered by tests

res_json = response.json()
return res_json.get("type") == "whartonMBA" or res_json.get("type") == "whartonUGR"
except (ConnectTimeout, ReadTimeout, KeyError, ConnectionError):
return None

Check warning on line 61 in backend/gsr_booking/models.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/models.py#L58-L61

Added lines #L58 - L61 were not covered by tests

class Meta:
verbose_name = "Group Membership"
Expand Down Expand Up @@ -104,7 +120,7 @@
start = models.DateTimeField(default=timezone.now)
end = models.DateTimeField(default=timezone.now)
creator = models.ForeignKey(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE, null=True, blank=True)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
is_cancelled = models.BooleanField(default=False)
reminder_sent = models.BooleanField(default=False)

Expand All @@ -120,7 +136,3 @@
start = models.DateTimeField(default=timezone.now)
end = models.DateTimeField(default=timezone.now)
is_cancelled = models.BooleanField(default=False)


# import at end to prevent circular dependency
from gsr_booking.api_wrapper import WhartonGSRBooker # noqa: E402
2 changes: 2 additions & 0 deletions backend/gsr_booking/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
BookRoom,
CancelRoom,
CheckWharton,
CreditsView,
GroupMembershipViewSet,
GroupViewSet,
Locations,
Expand All @@ -32,4 +33,5 @@
path("book/", BookRoom.as_view(), name="book"),
path("cancel/", CancelRoom.as_view(), name="cancel"),
path("reservations/", ReservationsView.as_view(), name="reservations"),
path("credits/", CreditsView.as_view(), name="credits"),
]
61 changes: 31 additions & 30 deletions backend/gsr_booking/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from rest_framework.response import Response
from rest_framework.views import APIView

from gsr_booking.api_wrapper import APIError, GSRBooker, WhartonGSRBooker
from gsr_booking.api_wrapper import APIError, BookingWrapper
from gsr_booking.group_logic import GroupBook
from gsr_booking.models import GSR, Group, GroupMembership, GSRBooking
from gsr_booking.serializers import (
GroupMembershipSerializer,
Expand Down Expand Up @@ -163,6 +164,11 @@
)


# umbrella class used for accessing GSR API's (needed for token authentication)
BW = BookingWrapper()
GB = GroupBook()


class Locations(generics.ListAPIView):
"""Lists all available locations to book from"""

Expand All @@ -186,11 +192,8 @@


class CheckWharton(APIView):

permission_classes = [IsAuthenticated]

def get(self, request):
return Response({"is_wharton": WhartonGSRBooker.is_wharton(request.user)})
return Response({"is_wharton": BW.is_wharton(request.user)})


class Availability(APIView):
Expand All @@ -208,16 +211,11 @@
end = request.GET.get("end")

try:
return Response(
GSRBooker.get_availability(
lid,
gid,
start,
end,
request.user,
request.user.booking_groups.filter(name="Penn Labs").first(),
)
)
group = Group.objects.get(name="Penn Labs")
if request.user in group.members.all():
return Response(GB.get_availability(lid, gid, start, end, request.user, group))

Check warning on line 216 in backend/gsr_booking/views.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/views.py#L216

Added line #L216 was not covered by tests
else:
return Response(BW.get_availability(lid, gid, start, end, request.user))
except APIError as e:
return Response({"error": str(e)}, status=400)

Expand All @@ -235,15 +233,11 @@
room_name = request.data["room_name"]

try:
GSRBooker.book_room(
gid,
room_id,
room_name,
start,
end,
request.user,
request.user.booking_groups.filter(name="Penn Labs").first(),
)
group = Group.objects.get(name="Penn Labs")
if request.user in group.members.all():
GB.book_room(gid, room_id, room_name, start, end, request.user, group)

Check warning on line 238 in backend/gsr_booking/views.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/views.py#L238

Added line #L238 was not covered by tests
else:
BW.book_room(gid, room_id, room_name, start, end, request.user)
return Response({"detail": "success"})
except APIError as e:
return Response({"error": str(e)}, status=400)
Expand All @@ -260,7 +254,7 @@
booking_id = request.data["booking_id"]

try:
GSRBooker.cancel_room(booking_id, request.user)
BW.cancel_room(booking_id, request.user)
return Response({"detail": "success"})
except APIError as e:
return Response({"error": str(e)}, status=400)
Expand All @@ -274,8 +268,15 @@
permission_classes = [IsAuthenticated]

def get(self, request):
return Response(
GSRBooker.get_reservations(
request.user, request.user.booking_groups.filter(name="Penn Labs").first()
)
)
return Response(BW.get_reservations(request.user))


class CreditsView(APIView):
"""
Gets credits for a User
"""

permission_classes = [IsAuthenticated]

def get(self, request):
return Response(BW.check_credits(request.user))

Check warning on line 282 in backend/gsr_booking/views.py

View check run for this annotation

Codecov / codecov/patch

backend/gsr_booking/views.py#L282

Added line #L282 was not covered by tests
1 change: 0 additions & 1 deletion backend/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
max-line-length = 100
exclude = .venv, migrations
inline-quotes = double
ignore = E203, W503

[isort]
default_section = THIRDPARTY
Expand Down
16 changes: 8 additions & 8 deletions backend/tests/gsr_booking/test_gsr_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,21 @@ def test_recent(self):
self.assertIn("image_url", res_json[0])
self.assertNotEqual(res_json[0]["id"], res_json[1]["id"])

@mock.patch("gsr_booking.api_wrapper.WhartonBookingWrapper.is_wharton", is_wharton_false)
@mock.patch("gsr_booking.views.BW.is_wharton", is_wharton_false)
def test_get_wharton_false(self):
response = self.client.get(reverse("is-wharton"))
res_json = json.loads(response.content)
self.assertEqual(1, len(res_json))
self.assertFalse(res_json["is_wharton"])

@mock.patch("gsr_booking.api_wrapper.WhartonBookingWrapper.is_wharton", is_wharton_true)
@mock.patch("gsr_booking.views.BW.is_wharton", is_wharton_true)
def test_get_wharton_true(self):
response = self.client.get(reverse("is-wharton"))
res_json = json.loads(response.content)
self.assertEqual(1, len(res_json))
self.assertTrue(res_json["is_wharton"])

@mock.patch("gsr_booking.api_wrapper.BookingHandler.get_availability", libcal_availability)
@mock.patch("gsr_booking.views.BW.get_availability", libcal_availability)
def test_availability_libcal(self):
response = self.client.get(reverse("availability", args=["1086", "1889"]))
res_json = json.loads(response.content)
Expand All @@ -120,7 +120,7 @@ def test_availability_libcal(self):
self.assertIn("id", room)
self.assertIn("availability", room)

@mock.patch("gsr_booking.api_wrapper.BookingHandler.get_availability", wharton_availability)
@mock.patch("gsr_booking.views.BW.get_availability", wharton_availability)
def test_availability_wharton(self):
response = self.client.get(reverse("availability", args=["JMHH", "1"]))
res_json = json.loads(response.content)
Expand All @@ -134,7 +134,7 @@ def test_availability_wharton(self):
self.assertIn("id", room)
self.assertIn("availability", room)

@mock.patch("gsr_booking.api_wrapper.BookingHandler.book_room", book_cancel_room)
@mock.patch("gsr_booking.views.BW.book_room", book_cancel_room)
def test_book_libcal(self):
payload = {
"start_time": "2021-11-21T18:30:00-05:00",
Expand All @@ -150,7 +150,7 @@ def test_book_libcal(self):
self.assertEqual(1, len(res_json))
self.assertEqual("success", res_json["detail"])

@mock.patch("gsr_booking.api_wrapper.BookingHandler.book_room", book_cancel_room)
@mock.patch("gsr_booking.views.BW.book_room", book_cancel_room)
def test_book_wharton(self):
payload = {
"start_time": "2021-11-21T18:30:00-05:00",
Expand All @@ -166,7 +166,7 @@ def test_book_wharton(self):
self.assertEqual(1, len(res_json))
self.assertEqual("success", res_json["detail"])

@mock.patch("gsr_booking.api_wrapper.BookingHandler.cancel_room", book_cancel_room)
@mock.patch("gsr_booking.views.BW.cancel_room", book_cancel_room)
def test_cancel_room(self):
payload = {"booking_id": "booking id"}
response = self.client.post(
Expand All @@ -176,7 +176,7 @@ def test_cancel_room(self):
self.assertEqual(1, len(res_json))
self.assertEqual("success", res_json["detail"])

@mock.patch("gsr_booking.api_wrapper.BookingHandler.get_reservations", reservations)
@mock.patch("gsr_booking.views.BW.get_reservations", reservations)
def test_reservations(self):
response = self.client.get(reverse("reservations"))
res_json = json.loads(response.content)
Expand Down
Loading
Loading