1+ import zoneinfo
2+
13import requests
2- from bs4 import BeautifulSoup
3- from dateutil import parser
4+ from django .conf import settings
45from 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
710from penndata .models import FitnessRoom , FitnessSnapshot
811
@@ -12,77 +15,68 @@ def cap_string(s):
1215
1316
1417def 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
6645class 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
0 commit comments