Skip to content

Commit fac021e

Browse files
Update Fitness for new API (#368)
* Update fitness to new API * Make image files reflect exact name * fixed O(n^2), added more tests --------- Co-authored-by: minghansun1 <minghansun@hotmail.com>
1 parent 566de03 commit fac021e

7 files changed

Lines changed: 342 additions & 178 deletions

File tree

Pipfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[[source]]
2+
url = "https://pypi.org/simple"
3+
verify_ssl = true
4+
name = "pypi"
5+
6+
[packages]
7+
8+
[dev-packages]
9+
10+
[requires]
11+
python_version = "3.11"

backend/penndata/management/commands/get_fitness_snapshot.py

Lines changed: 47 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import zoneinfo
2+
13
import requests
2-
from bs4 import BeautifulSoup
3-
from dateutil import parser
4+
from django.conf import settings
45
from django.core.management.base import BaseCommand
5-
from django.utils import timezone
6+
from django.db.models import Q
7+
from django.utils.dateparse import parse_datetime
8+
from django.utils.timezone import make_aware
69

710
from penndata.models import FitnessRoom, FitnessSnapshot
811

@@ -12,77 +15,68 @@ def cap_string(s):
1215

1316

1417
def get_usages():
15-
16-
# count/capacities default to 0 since spreadsheet number appears blank if no one there
17-
locations = [
18-
"4th Floor Fitness",
19-
"3rd Floor Fitness",
20-
"2nd Floor Strength",
21-
"Basketball Courts",
22-
"MPR",
23-
"Climbing Wall",
24-
"1st Floor Fitness",
25-
"Pool-Shallow",
26-
"Pool-Deep",
27-
]
28-
usages = {location: {"count": 0, "capacity": 0} for location in locations}
29-
30-
date = timezone.localtime() # default if can't get date from spreadsheet
31-
3218
try:
3319
resp = requests.get(
34-
(
35-
"https://docs.google.com/spreadsheets/u/0/d/e/"
36-
"2PACX-1vSX91_MlAjJo5uVLznuy7BFnUgiBOI28oBCReLRKKo76L"
37-
"-k8EFgizAYXpIKPBX_c76wC3aztn3BogD4"
38-
"/pubhtml/sheet?headers=false&gid=0"
39-
)
20+
"https://goboardapi.azurewebsites.net/api/FacilityCount/GetCountsByAccount",
21+
params={"AccountAPIKey": settings.FITNESS_TOKEN},
4022
)
23+
data = resp.json()
4124
except ConnectionError:
4225
return None
43-
44-
html = resp.content.decode("utf8")
45-
soup = BeautifulSoup(html, "html5lib")
46-
if not (embedded_spreadsheet := soup.find("tbody")):
26+
except requests.exceptions.JSONDecodeError:
4727
return None
4828

49-
table_rows = embedded_spreadsheet.findChildren("tr")
50-
for i, row in enumerate(table_rows):
51-
cells = row.findChildren("td")
52-
if i == 0:
53-
date = timezone.make_aware(parser.parse(cells[1].getText()))
54-
elif (location := cap_string(cells[0].getText())) in usages:
55-
try:
56-
count = int(cells[1].getText())
57-
capacity = float(cells[2].getText().strip("%"))
58-
usages[location] = {"count": count, "capacity": capacity}
59-
except ValueError:
60-
pass
61-
else:
62-
print(f"Unknown location: {location}")
63-
return usages, date
29+
def location_aware_datetime(time_str):
30+
date = parse_datetime(time_str)
31+
timezone = zoneinfo.ZoneInfo("America/New_York")
32+
return make_aware(date, timezone=timezone)
33+
34+
usages = {
35+
location["LocationName"]: {
36+
"count": location["LastCount"],
37+
"capacity": location["TotalCapacity"],
38+
"last_updated": location_aware_datetime(location["LastUpdatedDateAndTime"]),
39+
}
40+
for location in data
41+
}
42+
return usages
6443

6544

6645
class Command(BaseCommand):
67-
help = "Captures a new Fitness Snapshot for every Laundry room."
46+
help = "Captures a new Fitness Snapshot for every Fitness room."
6847

6948
def handle(self, *args, **kwargs):
70-
usage_by_location, date = get_usages()
49+
# Don't update locations for which we already have a room with a matching last_updated date.
50+
# Fixed the O(n^2) issue by loading everything into memory. Should be fine since there's
51+
# not many rooms, and 1 snapshot returned per room
52+
all_rooms = FitnessRoom.objects.all()
53+
all_room_names = set(room.name for room in all_rooms)
54+
query = Q()
55+
for room_name, room_usage in get_usages().items():
56+
query |= Q(room__name=room_name, date=room_usage["last_updated"])
57+
existing_snapshots = FitnessSnapshot.objects.filter(query)
58+
existing_room_date_pairs = set(
59+
(snapshot.room.name, snapshot.date) for snapshot in existing_snapshots
60+
)
7161

72-
# prevent double creating FitnessSnapshots
73-
if FitnessSnapshot.objects.filter(date=date).exists():
74-
self.stdout.write("FitnessSnapshots already exist for this date!")
75-
return
62+
def exists(record):
63+
(name, usage) = record
64+
if name not in all_room_names:
65+
return False
66+
if (name, usage["last_updated"]) in existing_room_date_pairs:
67+
return False
68+
return True
7669

70+
usage_by_location = filter(exists, get_usages().items())
7771
FitnessSnapshot.objects.bulk_create(
7872
[
7973
FitnessSnapshot(
8074
room=FitnessRoom.objects.get_or_create(name=room_name)[0],
81-
date=date,
75+
date=room_usage["last_updated"],
8276
count=room_usage["count"],
8377
capacity=room_usage["capacity"],
8478
)
85-
for room_name, room_usage in usage_by_location.items()
79+
for (room_name, room_usage) in usage_by_location
8680
]
8781
)
8882

backend/penndata/management/commands/load_fitness_rooms.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,23 @@
66
class Command(BaseCommand):
77
def handle(self, *args, **kwargs):
88
fitness_rooms = [
9-
"4th Floor Fitness",
10-
"3rd Floor Fitness",
11-
"2nd Floor Strength",
12-
"Basketball Courts",
13-
"MPR",
149
"Climbing Wall",
10+
"Rec Lounge",
1511
"1st Floor Fitness",
16-
"Pool-Shallow",
17-
"Pool-Deep",
12+
"Court 1",
13+
"Court 2",
14+
"Court 3",
15+
"Multipurpose Room",
16+
"2nd Floor Weight Room",
17+
"3rd Floor Fitness Room",
18+
"4th Floor Fitness Room",
19+
"Studio 409",
20+
"Sheerr Pool",
1821
]
1922
for room in fitness_rooms:
2023
obj, _ = FitnessRoom.objects.get_or_create(name=room)
2124
if obj.image_url == "":
22-
s3_image_name = (
23-
room.replace(" ", "_") + (".png" if "2nd" in room else ".jpg")
24-
if "Pool" not in room
25-
else "Pool.jpeg"
26-
)
25+
s3_image_name = room.replace(" ", "_") + ".jpg"
2726
obj.image_url = (
2827
f"https://s3.us-east-2.amazonaws.com/penn.mobile/pottruck/{s3_image_name}"
2928
)

backend/pennmobile/settings/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@
180180
LIBCAL_SECRET = os.environ.get("LIBCAL_SECRET", None)
181181
WHARTON_TOKEN = os.environ.get("WHARTON_TOKEN", None)
182182

183+
# Fitness Token
184+
FITNESS_TOKEN = os.environ.get("FITNESS_TOKEN", None)
185+
183186
# Upload file storage
184187
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
185188
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID", None)

backend/tests/penndata/fitness_snapshot.html

Lines changed: 0 additions & 106 deletions
This file was deleted.

0 commit comments

Comments
 (0)