From 5c5cd19232f66257558cf88a596fae581f045a7d Mon Sep 17 00:00:00 2001 From: William Ma Date: Wed, 22 Dec 2021 14:48:05 -0500 Subject: [PATCH 001/305] Add Google Sheets for external eatery events --- src/dfg/ExternalEateries.py | 1 + src/dfg/GoogleSheetsEateries.py | 177 ++++++++++++++++++++++++++ src/urls.py | 5 +- src/views.py | 20 +++ static_sources/external_eateries.json | 2 +- 5 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 src/dfg/GoogleSheetsEateries.py diff --git a/src/dfg/ExternalEateries.py b/src/dfg/ExternalEateries.py index b34b711..d96e622 100644 --- a/src/dfg/ExternalEateries.py +++ b/src/dfg/ExternalEateries.py @@ -15,6 +15,7 @@ class ExternalEateries(DfgNode): + EXTERNAL_EATERIES_PATH = "static_sources/external_eateries.json" # based on date.weekday() diff --git a/src/dfg/GoogleSheetsEateries.py b/src/dfg/GoogleSheetsEateries.py new file mode 100644 index 0000000..4d7c9b7 --- /dev/null +++ b/src/dfg/GoogleSheetsEateries.py @@ -0,0 +1,177 @@ +import datetime +import json +import os +import re +from typing import Optional + +import pytz + +from src.datatype.Cafe import Cafe +from src.datatype.CafeEvent import CafeEvent +from src.datatype.CafeMenu import CafeMenu +from src.datatype.MenuItem import MenuItem +from src.dfg.DfgNode import DfgNode + +import os.path + +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError + + +class GoogleSheetsEateries(DfgNode): + + def __init__( + self, + spreadsheet_id: str, + external_eateries_path: str = 'static_sources/external_eateries.json', + credentials_path: str = 'credentials.json', + cached_token_path: str = 'token.json' + ): + self.spreadsheet_id = spreadsheet_id + self.external_eateries_path = external_eateries_path + self.credentials_path = credentials_path + self.token_cache_path = cached_token_path + + def __call__(self, *args, **kwargs): + eateries = [] + + with open(self.external_eateries_path) as f: + json_eateries = json.load(f)["eateries"] + + for json_eatery in json_eateries: + eateries.append(self.cafe_from_json(json_eatery)) + + return eateries + + def cafe_from_json(self, json_eatery: dict) -> Cafe: + return Cafe( + name=json_eatery["name"], + campus_area=json_eatery["campusArea"]["descrshort"], + events=self.cafe_events_from_google_sheets( + table_name=json_eatery["name"], + ), + latitude=json_eatery["coordinates"]["latitude"], + longitude=json_eatery["coordinates"]["longitude"], + menu=GoogleSheetsEateries.cafe_menu_from_json( + json_eatery["diningItems"] + ) + ) + + def cafe_events_from_google_sheets( + self, + table_name: str, + ) -> list[CafeEvent]: + scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"] + creds = None + if os.path.exists(self.token_cache_path): + creds = Credentials.from_authorized_user_file(self.token_cache_path, scopes) + + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file("credentials.json", scopes) + creds = flow.run_local_server(port=0) + + with open(self.token_cache_path, "w") as token: + token.write(creds.to_json()) + + try: + service = build("sheets", "v4", credentials=creds) + sheet = service.spreadsheets() + result = sheet.values().get( + spreadsheetId=self.spreadsheet_id, + range=f"{table_name}!A:C" + ).execute() + values = result.get("values", []) + + except HttpError as err: + print(f"{self}: {err}") + return [] + + events = [] + for row in values: + event = self.parse_row(row) + if event is not None: + events.append(event) + + return events + + def parse_row(self, row: list[str]) -> Optional[CafeEvent]: + if len(row) != 3: + return None + + try: + date = datetime.date.fromisoformat(row[0]) + + except ValueError: + return None + + start_time = self.parse_time(row[1]) + end_time = self.parse_time(row[2]) + + if start_time is None or end_time is None: + return None + + return CafeEvent( + canonical_date=date, + start_timestamp=GoogleSheetsEateries.timestamp_combined(date, start_time), + end_timestamp=GoogleSheetsEateries.timestamp_combined(date, end_time) + ) + + def parse_time(self, time_str: str) -> Optional[datetime.time]: + # time_str is like 10:00 AM or 3:00 PM + match = re.fullmatch(r'([0-9]?[0-9]):([0-9][0-9]) ([AP]M)', time_str) + if not match: + return None + + hours = int(match.group(1)) + minutes = int(match.group(2)) + is_pm = match.group(3) == "pm" + try: + return datetime.time( + hour=hours + (12 if is_pm else 0), + minute=minutes + ) + + except Exception as e: + print(f"{self}: e") + return None + + @staticmethod + def cafe_menu_from_json(json_dining_items: list) -> CafeMenu: + items = [] + + for json_dining_item in json_dining_items: + items.append(MenuItem( + healthy=json_dining_item["healthy"], + name=json_dining_item["item"] + )) + + return CafeMenu(items=items) + + @staticmethod + def timestamp_combined(date: datetime.date, time: datetime.time): + """ + Returns the Unix (UTC) timestamp of the combined (date, time) in the + New York timezone. + """ + + tz = pytz.timezone('America/New_York') + return int(tz.localize(datetime.datetime.combine(date, time)).timestamp()) + + def description(self): + return "ExternalEateries" + + +if __name__ == "__main__": + from src.dfg.EateryToJson import EateryToJson + + dfg = EateryToJson(GoogleSheetsEateries( + spreadsheet_id="1ImfeTUA6I1Ub-aavgIW53Pf7EVB694f1294NPSCRd5c", + )) + + print(json.dumps(dfg(), indent=2)) diff --git a/src/urls.py b/src/urls.py index 7ece5c9..4bf0e28 100644 --- a/src/urls.py +++ b/src/urls.py @@ -2,4 +2,7 @@ from . import views -urlpatterns = [path("", views.index, name="index")] +urlpatterns = [ + path("", views.index, name="index"), + path("gs", views.google_sheets_eateries, name="gs") +] diff --git a/src/views.py b/src/views.py index 70c084e..bf39652 100644 --- a/src/views.py +++ b/src/views.py @@ -9,6 +9,7 @@ from src.dfg.EateryGroupByType import EateryGroupByType from src.dfg.EateryToJson import EateryToJson from src.dfg.ExternalEateries import ExternalEateries +from src.dfg.GoogleSheetsEateries import GoogleSheetsEateries dataflow_graph = DictResponseWrapper( EateryToJson( @@ -31,3 +32,22 @@ def index(request): end=date.today() + timedelta(days=7) ) return JsonResponse(result) + + +def google_sheets_eateries(request): + dfg = DictResponseWrapper( + EateryToJson( + GoogleSheetsEateries( + spreadsheet_id="1ImfeTUA6I1Ub-aavgIW53Pf7EVB694f1294NPSCRd5c" + ) + ) + ) + + result = dfg( + tzinfo=pytz.timezone("US/Eastern"), + start=date.today(), + end=date.today() + timedelta(days=7) + ) + + return JsonResponse(result) + diff --git a/static_sources/external_eateries.json b/static_sources/external_eateries.json index aecbeb0..3c6ee49 100644 --- a/static_sources/external_eateries.json +++ b/static_sources/external_eateries.json @@ -88,7 +88,7 @@ { "slug": "Macs", "external": true, - "name": "Mac’s Café ", + "name": "Mac’s Café", "nameshort": "Mac's", "about": "", "cornellDining": false, From 25f7bd6f8610ec2f6864976cd459cbbca216b5bb Mon Sep 17 00:00:00 2001 From: William Ma Date: Thu, 23 Dec 2021 14:27:57 -0500 Subject: [PATCH 002/305] Add requirements.txt --- requirements.txt | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d759c0b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,33 @@ +asgiref==3.4.1 +black==21.12b0 +cachetools==4.2.4 +certifi==2021.10.8 +charset-normalizer==2.0.9 +click==8.0.3 +Django==4.0 +google-api-core==2.3.2 +google-api-python-client==2.33.0 +google-auth==2.3.3 +google-auth-httplib2==0.1.0 +google-auth-oauthlib==0.4.6 +googleapis-common-protos==1.54.0 +httplib2==0.20.2 +idna==3.3 +mypy-extensions==0.4.3 +oauthlib==3.1.1 +pathspec==0.9.0 +platformdirs==2.4.0 +protobuf==3.19.1 +pyasn1==0.4.8 +pyasn1-modules==0.2.8 +pyparsing==3.0.6 +pytz==2021.3 +requests==2.26.0 +requests-oauthlib==1.3.0 +rsa==4.8 +six==1.16.0 +sqlparse==0.4.2 +tomli==1.2.3 +typing_extensions==4.0.1 +uritemplate==4.1.1 +urllib3==1.26.7 From 0cee5675c9045896369a05d4271379ff018b7433 Mon Sep 17 00:00:00 2001 From: connor-momentranks Date: Sun, 26 Dec 2021 11:34:19 -0500 Subject: [PATCH 003/305] Wait time mock data, env setup --- .gitignore | 8 ++++++ {src => api}/__init__.py | 0 api/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 176 bytes api/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 176 bytes api/__pycache__/admin.cpython-39.pyc | Bin 0 -> 217 bytes api/__pycache__/apps.cpython-39.pyc | Bin 0 -> 447 bytes api/__pycache__/models.cpython-39.pyc | Bin 0 -> 586 bytes api/__pycache__/urls.cpython-38.pyc | Bin 0 -> 351 bytes api/__pycache__/urls.cpython-39.pyc | Bin 0 -> 351 bytes api/__pycache__/views.cpython-38.pyc | Bin 0 -> 1433 bytes api/__pycache__/views.cpython-39.pyc | Bin 0 -> 1433 bytes {src => api}/admin.py | 0 {src => api}/apps.py | 4 +-- {src => api}/datatype/Cafe.py | 8 +++--- {src => api}/datatype/CafeEvent.py | 2 +- {src => api}/datatype/CafeMenu.py | 2 +- {src => api}/datatype/DiningHall.py | 6 ++-- {src => api}/datatype/DiningHallEvent.py | 4 +-- {src => api}/datatype/DiningHallMenu.py | 2 +- .../datatype/DiningHallMenuCategory.py | 2 +- {src => api}/datatype/Eatery.py | 0 {src => api}/datatype/Event.py | 0 {src => api}/datatype/MenuItem.py | 0 {src => api}/datatype/__init__.py | 0 api/datatype/__pycache__/Cafe.cpython-39.pyc | Bin 0 -> 1695 bytes .../__pycache__/CafeEvent.cpython-39.pyc | Bin 0 -> 600 bytes .../__pycache__/CafeMenu.cpython-39.pyc | Bin 0 -> 830 bytes .../__pycache__/DiningHall.cpython-39.pyc | Bin 0 -> 1609 bytes .../DiningHallEvent.cpython-39.pyc | Bin 0 -> 1141 bytes .../__pycache__/DiningHallMenu.cpython-39.pyc | Bin 0 -> 897 bytes .../DiningHallMenuCategory.cpython-39.pyc | Bin 0 -> 947 bytes .../__pycache__/Eatery.cpython-39.pyc | Bin 0 -> 807 bytes api/datatype/__pycache__/Event.cpython-39.pyc | Bin 0 -> 1986 bytes .../__pycache__/MenuItem.cpython-39.pyc | Bin 0 -> 885 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 185 bytes {src => api}/dfg/Concat.py | 0 {src => api}/dfg/CornellDiningNow.py | 18 ++++++------ {src => api}/dfg/DfgNode.py | 0 {src => api}/dfg/DictResponseWrapper.py | 2 +- {src => api}/dfg/EateryGroupByType.py | 6 ++-- {src => api}/dfg/EateryToJson.py | 4 +-- {src => api}/dfg/ExternalEateries.py | 14 ++++----- {src => api}/dfg/GoogleSheetsEateries.py | 12 ++++---- {src => api}/dfg/InMemoryCache.py | 2 +- {src => api}/dfg/__init__.py | 0 api/dfg/__pycache__/Concat.cpython-38.pyc | Bin 0 -> 949 bytes api/dfg/__pycache__/Concat.cpython-39.pyc | Bin 0 -> 953 bytes .../CornellDiningNow.cpython-39.pyc | Bin 0 -> 5116 bytes api/dfg/__pycache__/DfgNode.cpython-38.pyc | Bin 0 -> 723 bytes api/dfg/__pycache__/DfgNode.cpython-39.pyc | Bin 0 -> 723 bytes .../DictResponseWrapper.cpython-39.pyc | Bin 0 -> 1199 bytes .../EateryGroupByType.cpython-39.pyc | Bin 0 -> 1367 bytes .../__pycache__/EateryToJson.cpython-39.pyc | Bin 0 -> 1715 bytes .../ExternalEateries.cpython-39.pyc | Bin 0 -> 4351 bytes .../GoogleSheetsEateries.cpython-39.pyc | Bin 0 -> 5405 bytes api/dfg/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 180 bytes api/dfg/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 180 bytes api/migrations/0001_initial.py | 25 ++++++++++++++++ {src => api}/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-39.pyc | Bin 0 -> 844 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 187 bytes {src => api}/models.py | 2 +- {src => api}/tests.py | 0 {src => api}/urls.py | 0 {src => api}/views.py | 18 ++++++------ eatery_blue_backend/settings.py | 1 + eatery_blue_backend/urls.py | 3 +- transactions/__init__.py | 0 .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 185 bytes transactions/__pycache__/admin.cpython-39.pyc | Bin 0 -> 226 bytes transactions/__pycache__/apps.cpython-39.pyc | Bin 0 -> 474 bytes .../__pycache__/models.cpython-39.pyc | Bin 0 -> 595 bytes transactions/__pycache__/urls.cpython-39.pyc | Bin 0 -> 405 bytes transactions/__pycache__/views.cpython-39.pyc | Bin 0 -> 1129 bytes transactions/admin.py | 3 ++ transactions/apps.py | 6 ++++ .../mock_vendor_controller.cpython-39.pyc | Bin 0 -> 1743 bytes .../controllers/mock_vendor_controller.py | 26 +++++++++++++++++ .../update_transactions_controller.py | 0 transactions/migrations/0001_initial.py | 25 ++++++++++++++++ transactions/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-39.pyc | Bin 0 -> 853 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 196 bytes transactions/models.py | 10 +++++++ transactions/scripts/fetch_transactions.py | 0 transactions/tests.py | 3 ++ transactions/urls.py | 8 ++++++ transactions/views.py | 27 ++++++++++++++++++ 88 files changed, 197 insertions(+), 56 deletions(-) create mode 100644 .gitignore rename {src => api}/__init__.py (100%) create mode 100644 api/__pycache__/__init__.cpython-38.pyc create mode 100644 api/__pycache__/__init__.cpython-39.pyc create mode 100644 api/__pycache__/admin.cpython-39.pyc create mode 100644 api/__pycache__/apps.cpython-39.pyc create mode 100644 api/__pycache__/models.cpython-39.pyc create mode 100644 api/__pycache__/urls.cpython-38.pyc create mode 100644 api/__pycache__/urls.cpython-39.pyc create mode 100644 api/__pycache__/views.cpython-38.pyc create mode 100644 api/__pycache__/views.cpython-39.pyc rename {src => api}/admin.py (100%) rename {src => api}/apps.py (67%) rename {src => api}/datatype/Cafe.py (87%) rename {src => api}/datatype/CafeEvent.py (86%) rename {src => api}/datatype/CafeMenu.py (80%) rename {src => api}/datatype/DiningHall.py (89%) rename {src => api}/datatype/DiningHallEvent.py (90%) rename {src => api}/datatype/DiningHallMenu.py (78%) rename {src => api}/datatype/DiningHallMenuCategory.py (87%) rename {src => api}/datatype/Eatery.py (100%) rename {src => api}/datatype/Event.py (100%) rename {src => api}/datatype/MenuItem.py (100%) rename {src => api}/datatype/__init__.py (100%) create mode 100644 api/datatype/__pycache__/Cafe.cpython-39.pyc create mode 100644 api/datatype/__pycache__/CafeEvent.cpython-39.pyc create mode 100644 api/datatype/__pycache__/CafeMenu.cpython-39.pyc create mode 100644 api/datatype/__pycache__/DiningHall.cpython-39.pyc create mode 100644 api/datatype/__pycache__/DiningHallEvent.cpython-39.pyc create mode 100644 api/datatype/__pycache__/DiningHallMenu.cpython-39.pyc create mode 100644 api/datatype/__pycache__/DiningHallMenuCategory.cpython-39.pyc create mode 100644 api/datatype/__pycache__/Eatery.cpython-39.pyc create mode 100644 api/datatype/__pycache__/Event.cpython-39.pyc create mode 100644 api/datatype/__pycache__/MenuItem.cpython-39.pyc create mode 100644 api/datatype/__pycache__/__init__.cpython-39.pyc rename {src => api}/dfg/Concat.py (100%) rename {src => api}/dfg/CornellDiningNow.py (91%) rename {src => api}/dfg/DfgNode.py (100%) rename {src => api}/dfg/DictResponseWrapper.py (95%) rename {src => api}/dfg/EateryGroupByType.py (87%) rename {src => api}/dfg/EateryToJson.py (92%) rename {src => api}/dfg/ExternalEateries.py (94%) rename {src => api}/dfg/GoogleSheetsEateries.py (95%) rename {src => api}/dfg/InMemoryCache.py (96%) rename {src => api}/dfg/__init__.py (100%) create mode 100644 api/dfg/__pycache__/Concat.cpython-38.pyc create mode 100644 api/dfg/__pycache__/Concat.cpython-39.pyc create mode 100644 api/dfg/__pycache__/CornellDiningNow.cpython-39.pyc create mode 100644 api/dfg/__pycache__/DfgNode.cpython-38.pyc create mode 100644 api/dfg/__pycache__/DfgNode.cpython-39.pyc create mode 100644 api/dfg/__pycache__/DictResponseWrapper.cpython-39.pyc create mode 100644 api/dfg/__pycache__/EateryGroupByType.cpython-39.pyc create mode 100644 api/dfg/__pycache__/EateryToJson.cpython-39.pyc create mode 100644 api/dfg/__pycache__/ExternalEateries.cpython-39.pyc create mode 100644 api/dfg/__pycache__/GoogleSheetsEateries.cpython-39.pyc create mode 100644 api/dfg/__pycache__/__init__.cpython-38.pyc create mode 100644 api/dfg/__pycache__/__init__.cpython-39.pyc create mode 100644 api/migrations/0001_initial.py rename {src => api}/migrations/__init__.py (100%) create mode 100644 api/migrations/__pycache__/0001_initial.cpython-39.pyc create mode 100644 api/migrations/__pycache__/__init__.cpython-39.pyc rename {src => api}/models.py (52%) rename {src => api}/tests.py (100%) rename {src => api}/urls.py (100%) rename {src => api}/views.py (69%) create mode 100644 transactions/__init__.py create mode 100644 transactions/__pycache__/__init__.cpython-39.pyc create mode 100644 transactions/__pycache__/admin.cpython-39.pyc create mode 100644 transactions/__pycache__/apps.cpython-39.pyc create mode 100644 transactions/__pycache__/models.cpython-39.pyc create mode 100644 transactions/__pycache__/urls.cpython-39.pyc create mode 100644 transactions/__pycache__/views.cpython-39.pyc create mode 100644 transactions/admin.py create mode 100644 transactions/apps.py create mode 100644 transactions/controllers/__pycache__/mock_vendor_controller.cpython-39.pyc create mode 100644 transactions/controllers/mock_vendor_controller.py create mode 100644 transactions/controllers/update_transactions_controller.py create mode 100644 transactions/migrations/0001_initial.py create mode 100644 transactions/migrations/__init__.py create mode 100644 transactions/migrations/__pycache__/0001_initial.cpython-39.pyc create mode 100644 transactions/migrations/__pycache__/__init__.cpython-39.pyc create mode 100644 transactions/models.py create mode 100644 transactions/scripts/fetch_transactions.py create mode 100644 transactions/tests.py create mode 100644 transactions/urls.py create mode 100644 transactions/views.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0994477 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +env/ +.env + + +eatery_blue_backend/__pycache__ + + +db.sqlite3 diff --git a/src/__init__.py b/api/__init__.py similarity index 100% rename from src/__init__.py rename to api/__init__.py diff --git a/api/__pycache__/__init__.cpython-38.pyc b/api/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..263f79a7e1069d71e1370b79baf37ad9003cf7fb GIT binary patch literal 176 zcmWIL<>g`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10WKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 oXQ$?+=oc3y>&M4u=4F<|$LkeT-r}&y%}*)KNwou6_Zf&80KjW4!2kdN literal 0 HcmV?d00001 diff --git a/api/__pycache__/__init__.cpython-39.pyc b/api/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b1bd62ad5b18dcf4b84f59698195f06abafe001 GIT binary patch literal 176 zcmYe~<>g`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10WKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 oXQ$?+=oc3y>&M4u=4F<|$LkeT-r}&y%}*)KNwou6_Zf&80LZH?%>V!Z literal 0 HcmV?d00001 diff --git a/api/__pycache__/admin.cpython-39.pyc b/api/__pycache__/admin.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5dfeb2319c3e3259ccfa5ffa835f00d1a902d352 GIT binary patch literal 217 zcmYjKI|{-;6x>Y^M2L8e6v5;oQ%Yp8BXRh`D~V}0;!>Ap69@TJp13*K zR9I8HvT%LDowyKfkk6LwJvh(s!C2>@G)Jo>%dBWLnA)>-Rb_Bt2CfP48eGeMmK3Ui cq~PKVMl$aN8%EQr?Z$I$Wust0apsLAU-c_F7XSbN literal 0 HcmV?d00001 diff --git a/api/__pycache__/apps.cpython-39.pyc b/api/__pycache__/apps.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..922acece0b46d2f08d0f0ea77615cbc9df053a01 GIT binary patch literal 447 zcmYjNJ5Iwu5Z(1UQpl%4v`DlrVqXBE1S05=5TbFj+IS|xu--LZ8x&d)1vlXi9Dp;p zrQ!-y%*K((Nb`ShG&5>E9x#&6w=eZh{@W+J5>hfHbw^}?0oSbHDd+5hK>*^Ffk6Hu z(g4D17WIAz#UkGB&C2r3nq|Jqd}UWSB~wy&Kn7XLff1Xh0)jLIK~q9c&pZ0_A>1jm zvN0^;!UAd+pXRGsQ`_?#HMByN`DN5=`%)TJpp@-EN&+_8$0I2p8l|_LeZZw^bS)Lp z<#NLheeZ)+{tFxPdzIWctXz^=V{BDnZf>oHWNx#jKvTPfPMhOnV(@8#s>W)abZ)ZH z4Nev+yGH{F?L}I;xLj9)hWkf4Z~8pso62P*+Tr#(kE{Z}gU9}F(?vt7wf*DntP}r0 I=dmO42Rl1)umAu6 literal 0 HcmV?d00001 diff --git a/api/__pycache__/models.cpython-39.pyc b/api/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..803641a1b0e42969c9ac545a8a8ab7749bf0895b GIT binary patch literal 586 zcmYjPy>in)5WbV-AQ?Fscmz5ZjtUwIh6#g*pMo?A%{4b??T#&Tx)XONf@>H$-T_M9 zglD*xDz8ArO3IA0n*I59rQMxxr51}hBk|w=sHdEjAVt1UzNR9D?20XCxAY!c@_5o(K<(=R-j|fX1*v~V2ASZ%M1tu)0}ja#E@qhi zJo&}M1M)ff;?B>>KO|g2N@KG4_}VGsRUH~@-Znm1cen*=Nu%ej`XV)&ZP;Bd7crO8 zs1~J+b17-7UJr38<)>chsp1m)G8V6P%Drw-L(GPJEY>REm=LG6GS)P;(h?{ci|eMn zE1mhET#$hh6t$gLpn)Td-~ewe3y#&D<%CAI-n3>Vrq;tJ)f4ox{fL ztcGfB>%K)3eD$*H*7&(Hc(X!U#2u>1tv0&H)kf7HX?mr)rkW0L-W}Y)`Ha*Lx6XOV t^YIz;m&9cOdu6uv95xdXv-iX8#`1BsDLSJLF@HYM_=5fz<_GS|>_59`p7{U( literal 0 HcmV?d00001 diff --git a/api/__pycache__/urls.cpython-38.pyc b/api/__pycache__/urls.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6d136373316436b22761a5594abebeaa671f47c GIT binary patch literal 351 zcmYk1J5Iwu5I}eROCX<;JGe;p0ucf!H4;K;+(eFNl)Qdi@4B(6ZHhGO%P|`y> z=tr>2O=;(y)8qm!aj{3_;aAIUN^47vYuX~Vw8p$eS0VKyuHZRrhCYPzN1pNXs8)oI zRAG&=&S7P?R)bpFqN~xgr0$z$iF;-6FgtfUs!N(}bceG|UOb}#MXpeOsd;nkhl|=m Sr}5U`L_@|n7>83i{rv-c=U&JF literal 0 HcmV?d00001 diff --git a/api/__pycache__/urls.cpython-39.pyc b/api/__pycache__/urls.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f8b2896809abf242e7ecc55c5609b576fadf444 GIT binary patch literal 351 zcmYk1J5B>J5J2tyB!PTN?vNt!1tJ7eY9xecwuzjb(Po3!UU_yCasW!Mfkek4IK{S9 zxdKJRIB6KkZ}eo%Pmjmfg5&M$TYqr=3GzRBEO-29!2ki$h)QNs3Wy*!Q59#g6euCV zc$+{9z4w@{&z3?zSP^AC7(9vT)sZ`hsqB(kH=CopNV+R=Po3z}(gMDQZ>uZJqJkc# z{cZ?bZ3{PVy&)fQiBAKPfUsJ2Q#e-`T+s%xp%v-|eTmc!xq|1gS^5+{9C^m?qk1Ov zq;hAi^BzmPaR$`V(@S z8S%-@(eGd%4dl>==u_;qr#wM{0Bt(tT8L7lOYl!}NDXIyn%P0KSwrx=|NW0>-9zY4 zvzUK;SUiKSK7dd}F-Iv5aY{mh&6;p0bYOJ2n|h&VZ7%oIN?5UTj|XWrtlGKHYiT{K z+j)gI(q`DS^MJR~g>b>ntGu0d!j7HS_;=}IxQG#}vjx^<6;?e%taFCO$GuB z!X?_It=~|1gD%iEjLVelqHgCSM2xzmaA~AjQPoMxDC0WncX849S&3|isa#}=!P?&t zSsZDYwKs&!80VWwmSp3;I0D<^W)kb!>DMyKb0)#u*#gIM`cjHX{$jc}&5frxo|JhwR)6L=Nzvf>Yh8NMEmam8-VfppK62M-d3bcU>}XU zD{LrD)s<8D`XtFlqVSZCq%Itm(Qc)1^Qk^50>kh`WUOcb(`dxS(Qphn4kQ65g{K8L zktRmzf^?mtBH6DKrnKys1HNQx!u1uB-^23LPH&)?RJ~YanUInt*@57+w<+RD$}+8b z&+~kf9riMIw8|{%UOBAp^9ftskK(rwT~EolcbKpvwU$o{FUcr7z76hG3k2aBCeVD` z{@eHD9kBdA&u6f!kp_|!Q*v%o>=c(&sdI_7ci{uwQY+M@o~7$sy1_NwO8?~U`gS^E zdxPhXw$}&yt5I|~-hTb?{;SdBtzTX|dh%eje(%YH{;S=M9r`eaf4!x0$s(#uIt&vk z8xV(V8ZoaR+aTSlH2;&V8mSvuGIGO+VJ}yV%#oKj!2p#x8p~T|%K$IMuql?uHUo$D zPBpYxAvb@7J!Os}OAW|7%`e8U>rI+ZPI!B^{f*MID8gH_6Sjcnt562U!_wFLWkFhi zU+(^IZA$Yo4DZZ-`K>Z7=`X`Mn@twS99_%VA`YIWf=)R5$<(j93(_V5Zjc6^Ys4g{ RX)bpEF{nvE{_>ka?O&Q8Z&&~T literal 0 HcmV?d00001 diff --git a/api/__pycache__/views.cpython-39.pyc b/api/__pycache__/views.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59834d44fd48ecf7618df377be537387cd5519e7 GIT binary patch literal 1433 zcmZ{k%WfMt6oyI8MWd^w+Od-+-E|`%mf$oO1PF>Gwh_1sBk-jGxDm!8B~Qv3PD#qP zjCkd)3iuuDrGY+3(MOnVS9yX00owGCV7T_AHV({_X33e zw2S#CgvE2{`Zo}U7#1kS6sLp|Y}Z71)PvCzej3og*?bYE6cHF!szDgJAB1XK<7kHCbc=a6d&N&*Bb8H){v&IFX zORULSzaV;xEwDC>%Z%)zZs$EjjJl-oS!{SwHA%`D7bf2B;-c|`me~&1xy&?&b+|6G zVQgU5UY9E4LTn^il8v|JG1wM2lA)QMexu?%=L*c7O>nHHuaunRFQLkr;7}F|XHU2W+tMp3$Aa%3a&Gj@zSqTcQSpI_6U_(wWXN-qKzKuJ|HsYa!B~O5 z4qZP0G3XLAe1*@jA@8v9F8wQXqX8q#yTB`m`4`?9G2uJF24I5i54HW>{${LgVDGKF zYiub^^|e=o<}}GhvIw+^l_@-)v2LaC^Qk#4BFpepX1r(t(|9E0@n8%%4iy0>MPMX2 zQ8q^9gLJ*3qWI4fu8r#01EJ!2BFr^XU&HdlPJdr>rTasfWl|}gWQS6){)QY*Ql1&z zf05@K{HUMt<5liZ_se1RKuq}RK|K5kqU*Wh~j&5{Ax3YbDe{Cxr z@xA>QkGIzL4_4#&XuS32(RZ&$Pd0yi`S|I>(b|Kj54T_MuJ5o%LwM^gohu$QZPQ_p zFx7xKRMU$63#tv$tt$JTRMkq|%952^RxEq9Vr7oJx(x=X%+Xlgv0D~+DV9yKJa!p4 zaCho~!wR|iE$nG~6gg@@?xX(^dPHzkG7Qx>u-jcJB3o59AIkgP)i$Q0&fdn^SoRK$ z$z2-I7QCSzMA9pZOu=^s(c7PlpVynRpB#F3w*7_Db13MY*$G!b^Hr!K>tX5h{jwk( zz)yGow>Fji7}9&QUw)}fOZv-j&SjIsF-O;Nc8H^Asbmwuzqj?P?}M~Sgd3!R=Nhre SX`7Gze=KSek-x%bRQ(qbQg0Ff literal 0 HcmV?d00001 diff --git a/src/admin.py b/api/admin.py similarity index 100% rename from src/admin.py rename to api/admin.py diff --git a/src/apps.py b/api/apps.py similarity index 67% rename from src/apps.py rename to api/apps.py index 7dbae45..878e7d5 100644 --- a/src/apps.py +++ b/api/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class SrcConfig(AppConfig): +class ApiConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" - name = "src" + name = "api" diff --git a/src/datatype/Cafe.py b/api/datatype/Cafe.py similarity index 87% rename from src/datatype/Cafe.py rename to api/datatype/Cafe.py index c1ff2c9..d7c25fc 100644 --- a/src/datatype/Cafe.py +++ b/api/datatype/Cafe.py @@ -3,10 +3,10 @@ import pytz -from src.datatype.Eatery import Eatery -from src.datatype.Event import filter_range -from src.datatype.CafeEvent import CafeEvent -from src.datatype.CafeMenu import CafeMenu +from api.datatype.Eatery import Eatery +from api.datatype.Event import filter_range +from api.datatype.CafeEvent import CafeEvent +from api.datatype.CafeMenu import CafeMenu class Cafe(Eatery): diff --git a/src/datatype/CafeEvent.py b/api/datatype/CafeEvent.py similarity index 86% rename from src/datatype/CafeEvent.py rename to api/datatype/CafeEvent.py index 4dc7921..efbbf5b 100644 --- a/src/datatype/CafeEvent.py +++ b/api/datatype/CafeEvent.py @@ -1,4 +1,4 @@ -from src.datatype.Event import Event +from api.datatype.Event import Event class CafeEvent(Event): diff --git a/src/datatype/CafeMenu.py b/api/datatype/CafeMenu.py similarity index 80% rename from src/datatype/CafeMenu.py rename to api/datatype/CafeMenu.py index f3a58d1..5d9ab36 100644 --- a/src/datatype/CafeMenu.py +++ b/api/datatype/CafeMenu.py @@ -1,4 +1,4 @@ -from src.datatype.MenuItem import MenuItem +from api.datatype.MenuItem import MenuItem class CafeMenu: diff --git a/src/datatype/DiningHall.py b/api/datatype/DiningHall.py similarity index 89% rename from src/datatype/DiningHall.py rename to api/datatype/DiningHall.py index 9fcb9ec..df81976 100644 --- a/src/datatype/DiningHall.py +++ b/api/datatype/DiningHall.py @@ -3,9 +3,9 @@ import pytz -from src.datatype.Eatery import Eatery -from src.datatype.DiningHallEvent import DiningHallEvent -from src.datatype.Event import filter_range +from api.datatype.Eatery import Eatery +from api.datatype.DiningHallEvent import DiningHallEvent +from api.datatype.Event import filter_range class DiningHall(Eatery): diff --git a/src/datatype/DiningHallEvent.py b/api/datatype/DiningHallEvent.py similarity index 90% rename from src/datatype/DiningHallEvent.py rename to api/datatype/DiningHallEvent.py index c27a1c0..d0d1e83 100644 --- a/src/datatype/DiningHallEvent.py +++ b/api/datatype/DiningHallEvent.py @@ -1,7 +1,7 @@ from typing import Optional -from src.datatype.Event import Event -from src.datatype.DiningHallMenu import DiningHallMenu +from api.datatype.Event import Event +from api.datatype.DiningHallMenu import DiningHallMenu from datetime import date diff --git a/src/datatype/DiningHallMenu.py b/api/datatype/DiningHallMenu.py similarity index 78% rename from src/datatype/DiningHallMenu.py rename to api/datatype/DiningHallMenu.py index ffd68f5..596a2bc 100644 --- a/src/datatype/DiningHallMenu.py +++ b/api/datatype/DiningHallMenu.py @@ -1,4 +1,4 @@ -from src.datatype.DiningHallMenuCategory import DiningHallMenuCategory +from api.datatype.DiningHallMenuCategory import DiningHallMenuCategory class DiningHallMenu: diff --git a/src/datatype/DiningHallMenuCategory.py b/api/datatype/DiningHallMenuCategory.py similarity index 87% rename from src/datatype/DiningHallMenuCategory.py rename to api/datatype/DiningHallMenuCategory.py index c705976..d00e7d5 100644 --- a/src/datatype/DiningHallMenuCategory.py +++ b/api/datatype/DiningHallMenuCategory.py @@ -1,4 +1,4 @@ -from src.datatype.MenuItem import MenuItem +from api.datatype.MenuItem import MenuItem class DiningHallMenuCategory: diff --git a/src/datatype/Eatery.py b/api/datatype/Eatery.py similarity index 100% rename from src/datatype/Eatery.py rename to api/datatype/Eatery.py diff --git a/src/datatype/Event.py b/api/datatype/Event.py similarity index 100% rename from src/datatype/Event.py rename to api/datatype/Event.py diff --git a/src/datatype/MenuItem.py b/api/datatype/MenuItem.py similarity index 100% rename from src/datatype/MenuItem.py rename to api/datatype/MenuItem.py diff --git a/src/datatype/__init__.py b/api/datatype/__init__.py similarity index 100% rename from src/datatype/__init__.py rename to api/datatype/__init__.py diff --git a/api/datatype/__pycache__/Cafe.cpython-39.pyc b/api/datatype/__pycache__/Cafe.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bae7646681646711212dba2dacdaafd6c3e2807 GIT binary patch literal 1695 zcmaJ>y>A;g6eoGS(@8oxuG6?p3lyCSXt0!_qhYj&jZRgEpvwWp;c}$hQ+OXCsRR<# zNd_`?={r_P-kWaxX8)Y?f~68zocJ$}58-(x-)^a!+HfB89IhlKow zliQ8M)^00<(eA{9+3MVTp91!>^rK=G8rKd!H0-p zwR4l@TIMd2=z(#u%57=qLb|SM>hq1rEA(jR0Xl+mupQ>PGnXy&cdF3z&DE^U@+j53L!0 z*|w+h#kiI)kEGA{c=I`Ws^;?OXgBe2DN6;lbZ z2fw_@P(~fwWHZy$L+04%kqf<%Z~5bT$Eq~8XsY&P1n2z$gDgMwPHnf3j#X3S%AA0( zTRM#Xl`hD{Ewlb#uRq3>A0fOIUsQYs!ypYvhtosKeUD!+liOwugUJMFfUu9G$7;+n z*UPf15p#udl4Z~5x!P(1W3}Vxf{x&57fw}^Ta7vLywg~Uj<*ZDbg?Zfxom2wG2%Yy z-efn}Rze|_f)e}3`1`odGb9EH>1!~aF=vzkhY{JI`v-(Vf5TXeynd7P?#?yt9qzdp zA5S#FUk^-Qv}Jwn4b&JMcOR-}w5y)c=2g{540mu>gYS{2xI4U~z(}5XrH{pfca-@2 fZeoKr-E!2sjE=hacoXRpWT?+qLK7A7LUV!&NVrR_C z#48{nahF2dlkST@<^0`uKGke)0>=Btn`$wDPa>-kBRRqJhX@)p%OGb%#^4-ur1=aq zH*Cs>k&Z8*-T1;PXtSzu_F!yCiLFM2GsfH_upT) b-h-o(x?n|wp2U$-l^o4`AJd9zxo!Rn9cPc< literal 0 HcmV?d00001 diff --git a/api/datatype/__pycache__/CafeMenu.cpython-39.pyc b/api/datatype/__pycache__/CafeMenu.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2214f33780addcb00cb903ad4e944be72cbe3a7 GIT binary patch literal 830 zcmZuvy>1gh5T3ms`>-4-pit0IAtV>R;{hxS6iX-tZX%kKV70m3z=iwE?p=^0Q^hYp zPXT4#gMyj{XnE1ai1}4k~FI3SS)hbWuV0Ufd9^AGU_R+ZpJ;d$L>*%FX+C)iN6s6WGE#8)yj230mLM!C`>7JO(yOT3 zrYBjWrY9nKrwSQ~DvhM5MZKz2)K?l-D;sd07HQ4-0ADk0#q9F#Hln{x7+&{_>@Aml zbR;%q18X>g1za&?ScS^sQ}aMXqKg>PRhKRQ!js`z~!TD)i(&?EGrX{nK>GLc-LvBiS8{_O;C4G z`Zny}23*pdIWI)6IJYCt^HMe$*<;Sn8ip|Wsk5iD zYGEp#6be>TomJ;D$76O|upSr42YdjG2tiY{pMpA$}!Juob`(U$VSx4g#(rHKi%RllaT zo2I$Sj7igL^6H1tw?=9+%Im7CwU$NoW39yKw9aQG+By27X-?(SQ6--pNf+1A>UZ== z&E(OeEPpI3G0K`^1i6s4^G1%gE*v&aEU+t@Z9FdMXsqMFoT zcbnB;8AwL!FzN(@HDz#?*m+OjavE+!d--S5f1-_ zcThrd??X2p00|bicG#_4`xc1~C^{1NK>!L+ZNj?*=>-HC$-HEzS#S8j`i|Ti z*4XG+6{gMWvN`z-&ie~GS$wi%()y6$v8wY-nG+B;8?OET%9~X}eF*31_YmH2-#wW7 z2)ei`#GCI$+~5L>gfq^BRjRO>LB>Wi4h3O!iKnC-7KK9@>C0d2Y8LOmCM3?|l;!C;2m zqaI`_;H!fJLg9Puc_HTQ>o~eO*0>WMju4+hB;YQGES@*;^f`L^V^B#Bp+<(A8X2zM zNR45)AKX~k`s)l|8fS5G`-aMD!DTq!zGILx4Gps4@yeMem}xF7F^#>@1Eewg2XjDf A`2YX_ literal 0 HcmV?d00001 diff --git a/api/datatype/__pycache__/DiningHallEvent.cpython-39.pyc b/api/datatype/__pycache__/DiningHallEvent.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9dacd853a3a8e91b43dc2048c575025316711db2 GIT binary patch literal 1141 zcmZ8gy>1jS5Vm){KbPDkQTT_BiYt<(qCyD7A!rB%qS?mUtPSkp^)B{aP@?M+DHU&! z0?IrBJx_o{|!ujA<%yQ_+ESq3HgPC#l~Rp7|f2q z2_mQ>HO(nS9H^iU^U&i^u{z2lkC}?=Bu^-LPDCW)BO+oM9)@`;lGkLI{)U~%kh=6$ z+f|J&6yWIDp41(HwNa%j{q9Af)Jv%+@P~<_lO?WMZ1MTPJ^&y|P6f#W8A>K2=&?+0 z0oRl0n7pMT5aCfDPG=%IB6;taFp{qTli00aoaPCfMs8I|TbjyKb8BUx8(o!!;>g0S z+paJj@2Xk?s#`aZS}Y7%4V=q4H>8L-0t0P{y$h}*hh$8r!8DvQK|h0fQ#vI)m`313 zL4a4VGYm}+Vhz^Dtb!Xd7uiWGjY~NPg*whnij#euj2*L5y`BBJywC@nQrF|WRE4#i zpOQ0~y|L2RtZcMyjFFXo-zbrdnsNdObT)g^wj;ThY58eOLf&RSn|-!+)kJRX7Uc)X zJ1g2M16hi0zm?gNlH2XRSp#mEfki;lmd$e4TEz#!=8(w zOz^V&5pEM|w>_`jw!ixZuc*6oY2qR`e2ku$OYtS(3-Gl4_6wXmJLKfjhqII)VG%8| K>j!-bJ^TmJ%M54$ literal 0 HcmV?d00001 diff --git a/api/datatype/__pycache__/DiningHallMenu.cpython-39.pyc b/api/datatype/__pycache__/DiningHallMenu.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..121b158395b62b72e7408fe0907953f45b9d62bb GIT binary patch literal 897 zcmZuvJ&)5s5S_K15274G2!Uv5PC^P&Y<@r#fi4m>q&d-8LaROR5`1jdzFixXQ>ZR~ z;QA||y#GN-Md_A`nvRMYJBgBrm1aL@-`n?Q#nDj@u>JV?T^uujU$oe5J}zEhxF^_2 zkW9mz#f-r#ke>8cApI*Z_GEAhgVt}%fdQ)?PEwPa*=wQo8)eE9QK(sF7YQBjwk`Nm z+*J@W39+Yq6+A|w8&(qpaZ3iWwSu@U+ep%>?(U=CvZ|Lf45@O7w?ja4iW3Y+I+tt- z9AzL5o7w<(23{37rQh2YE^fhgaQG{uw@z6XC7CgqwJJ3qGA*M?mXvd43KzZ1^NIQt z8TEOnklHSyW*DC7QVq{U@=+NXi9C&@C`7TyRkZ);Fke(%&e5iV^EPt2`rYjE(LvrO zC&A)u8_l=6?%_hBS1VY;IZWV+p*nxMcY%^g?J0(PfZYs}-}S$+CHo5B;M%*u`qkR8 zdYvNU=Pom7jTb(zx@&V6^?B2E5u)mibn1#Eo9AQl_74VJK0A2-8Y>*>ED_p`ak9D8 z_|GiKKftyGcn4!+%fXKd*RWIk4rrqhbH#bp<9wdUQq#Q8`9&%8`XnGyODJel zspyt8S^AM=l%uZe-S_LA>A`pZ%p;8mG%99Hge9% literal 0 HcmV?d00001 diff --git a/api/datatype/__pycache__/DiningHallMenuCategory.cpython-39.pyc b/api/datatype/__pycache__/DiningHallMenuCategory.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d267d3c46f6dcdc53944c8d33c2913d2e1434941 GIT binary patch literal 947 zcmaJ=y>1gh5T4yT+lR3cML^Jm78mv*YRdw}5>VhKqB$vAU2Ye1VecoqXOJyZ#VD$*Y@eu>~PKRCc@bCoL?xT<( znTCu-jKMRITzV^z-WiX$^beue_PJoK%fw@SY@4Lq18iVH9Setzljl~u-uNs;G;F)Gd96k3Mk zBAI0>uWb0VEXV3yn5*}Fg{hc%xc>By^-T4TOSUkR_mJQxusEZ`lc7)2-xgODaPRz6r==wm3ML5RmWf&*Fj-@(Z1Z!ofKPbuR!EQM4uv z%_&8j)vVz;?`>X-T)ZPnsAxk}bi?vU@dZia-|&v4Gze9A4QKMc^Hpn#dR}7Wp+w*u zgSwXhn&ecGoN2D;EmchM4OCExjxJ+8(#bP2po(s}iq&WnoP|B{hP}CHS zE*YfT3)-k}K^?Y!O|OZBZWd%q=N2iXEC{FTa~p%RC3XzQpO4uGr>)D%)|l2>U71g9 zt+JE0TsGQxm%Zt_6a6(a`f8^8^4W0A&g!L}ofYM0ZB*uLnZY0mzv^^0(0tylLLy~l zDlaA0ay?(MKHA0iv3bfv?AMh!Z$spKVSV6`vlQ@VFKELW@7wd_hWvm*E{Gk$JuF~+ z`mNOc4pLzgxU*A)1B62a%$$jT_|Pqc{;%^aNbgeiIh#N zmNnW_DZea>dKcl&Tl)y}if-lCA@NnC*R9bOf11VC?PG*{7QKa3EOv)oxF-OKV1K7$ z==P2=L1P-<_3<t-8uw3shp literal 0 HcmV?d00001 diff --git a/api/datatype/__pycache__/Event.cpython-39.pyc b/api/datatype/__pycache__/Event.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2767634f0b2139242d8fd3252573a460dee8f3a9 GIT binary patch literal 1986 zcmZWqOHU(55bo}2kH^Na8$g7xc{uM(1UN!DMSwO6t0))efcD}j8V%D88JLIZ9+O}> ziWDQ^9CKwi5*+;xa?ii$YfkwKQKU^(kBwost*)+~s_y!#x~d$`&({fzU-G}g4_re2 z#?9SPfz4x>`V5F5f+i%T9ZHdB2}_-hld=v=d57C?PEzSqz9xc;$^{XXA?vu(kz7`e ztI`whb1tgFyWpLgtV{O|A}LDj>P)GqfmU72f!3UG_KDy42W%6c8t>a~5$9Q$_>SQs zETr*}7I7*KN7{~1^X}lVc??s34n&d;6{N!~A};82M=;@lol1!puyVjiCm5@u2G;AQ z^5jfr#n>?mQJCdf9EC|>tv}F3sEPnB1DAHqyv)RG8=>*(K%T+;3Z`BGGN1zzkO2cq z2M$ms$UYgUnu}h7 zJlt~H3zJf!5UFwEtWXco;%i8jkl@4KAs7*b!N&~H7%q=93cvpvb$Pwi5_iI1X5gpJoqSrOpHG#b~VhFn|M724Ba#TEnKZ?`)ZYXqFy$@u})w{4j zBgRImttaXul-laBHSsZQ*I`c3fBK^T=$o{w@~%|PP#u*GaI;yK!%p8@Z`w%g^xgF) zT)P7)i%DmzMm3j_=b5x6W}z64aklrAFlMd5V)3@iUb2e~R%3H*uw~{s I3rj2fe=9(&zyJUM literal 0 HcmV?d00001 diff --git a/api/datatype/__pycache__/MenuItem.cpython-39.pyc b/api/datatype/__pycache__/MenuItem.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d9d867444f322206d09a8924fce6e62d4857464 GIT binary patch literal 885 zcmZ`%y^hmB5Z+yHY$qfrAPTOeydt=xLg)}DAqvtU8W*e_?;i1C?}ptqC{ZMeOUoPF z3*6!?#W*}h~FcLcLqK>1$ zovP2eao7YryZ(0quOg&E06H%OS`Md1dNEzyIiT9V|Fstc(E+#(%9@-bQ#b(J2-}t4 zVW4&h9)A{2yy+%_N9V9Z!Z_tutMQ@owZjxoMF?D_5MdxhV`N*SJ{018Th!MtLkEkf z%0~IQk%7yq^!65{4SZ%y9U|wg9f55T?+r9~5Ei@d&IC`zA>I@gG)@_1KZj}eGPSt3 Re+7p0@eb1yjQ$QxegOd-#ghO4 literal 0 HcmV?d00001 diff --git a/api/datatype/__pycache__/__init__.cpython-39.pyc b/api/datatype/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccc7ec836bccd2e8820e38ad49b443a51e81b89d GIT binary patch literal 185 zcmYe~<>g`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10QKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 xXQ$?+=oc3y>!&1^B$iYbr0U1VXXa&=#K-FuRNmsS$<0qG%}KQb+4~uY832>CF<}4z literal 0 HcmV?d00001 diff --git a/src/dfg/Concat.py b/api/dfg/Concat.py similarity index 100% rename from src/dfg/Concat.py rename to api/dfg/Concat.py diff --git a/src/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py similarity index 91% rename from src/dfg/CornellDiningNow.py rename to api/dfg/CornellDiningNow.py index 82aa742..8660a1d 100644 --- a/src/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -1,15 +1,15 @@ from typing import Union -from src.dfg.DfgNode import DfgNode +from api.dfg.DfgNode import DfgNode import requests -from src.datatype.DiningHall import DiningHall -from src.datatype.Cafe import Cafe -from src.datatype.CafeMenu import CafeMenu -from src.datatype.CafeEvent import CafeEvent -from src.datatype.MenuItem import MenuItem -from src.datatype.DiningHallEvent import DiningHallEvent -from src.datatype.DiningHallMenuCategory import DiningHallMenuCategory -from src.datatype.DiningHallMenu import DiningHallMenu +from api.datatype.DiningHall import DiningHall +from api.datatype.Cafe import Cafe +from api.datatype.CafeMenu import CafeMenu +from api.datatype.CafeEvent import CafeEvent +from api.datatype.MenuItem import MenuItem +from api.datatype.DiningHallEvent import DiningHallEvent +from api.datatype.DiningHallMenuCategory import DiningHallMenuCategory +from api.datatype.DiningHallMenu import DiningHallMenu from datetime import date diff --git a/src/dfg/DfgNode.py b/api/dfg/DfgNode.py similarity index 100% rename from src/dfg/DfgNode.py rename to api/dfg/DfgNode.py diff --git a/src/dfg/DictResponseWrapper.py b/api/dfg/DictResponseWrapper.py similarity index 95% rename from src/dfg/DictResponseWrapper.py rename to api/dfg/DictResponseWrapper.py index 2e08ff9..17fd978 100644 --- a/src/dfg/DictResponseWrapper.py +++ b/api/dfg/DictResponseWrapper.py @@ -1,4 +1,4 @@ -from src.dfg.DfgNode import DfgNode +from api.dfg.DfgNode import DfgNode class DictResponseWrapper(DfgNode): diff --git a/src/dfg/EateryGroupByType.py b/api/dfg/EateryGroupByType.py similarity index 87% rename from src/dfg/EateryGroupByType.py rename to api/dfg/EateryGroupByType.py index 6bbb077..5aa2c3e 100644 --- a/src/dfg/EateryGroupByType.py +++ b/api/dfg/EateryGroupByType.py @@ -1,6 +1,6 @@ -from src.datatype.Cafe import Cafe -from src.datatype.DiningHall import DiningHall -from src.dfg.DfgNode import DfgNode +from api.datatype.Cafe import Cafe +from api.datatype.DiningHall import DiningHall +from api.dfg.DfgNode import DfgNode class EateryGroupByType(DfgNode): diff --git a/src/dfg/EateryToJson.py b/api/dfg/EateryToJson.py similarity index 92% rename from src/dfg/EateryToJson.py rename to api/dfg/EateryToJson.py index 7ec66e8..9ee9a7b 100644 --- a/src/dfg/EateryToJson.py +++ b/api/dfg/EateryToJson.py @@ -1,7 +1,7 @@ from typing import Union -from src.datatype.Eatery import Eatery -from src.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery +from api.dfg.DfgNode import DfgNode class EateryToJson(DfgNode): diff --git a/src/dfg/ExternalEateries.py b/api/dfg/ExternalEateries.py similarity index 94% rename from src/dfg/ExternalEateries.py rename to api/dfg/ExternalEateries.py index d96e622..6f8dde0 100644 --- a/src/dfg/ExternalEateries.py +++ b/api/dfg/ExternalEateries.py @@ -4,12 +4,12 @@ import pytz -from src.dfg.DfgNode import DfgNode -from src.datatype.Cafe import Cafe -from src.datatype.DiningHall import DiningHall -from src.datatype.CafeMenu import CafeMenu -from src.datatype.CafeEvent import CafeEvent -from src.datatype.MenuItem import MenuItem +from api.dfg.DfgNode import DfgNode +from api.datatype.Cafe import Cafe +from api.datatype.DiningHall import DiningHall +from api.datatype.CafeMenu import CafeMenu +from api.datatype.CafeEvent import CafeEvent +from api.datatype.MenuItem import MenuItem import json @@ -160,7 +160,7 @@ def description(self): if __name__ == "__main__": - from src.dfg.EateryToJson import EateryToJson + from api.dfg.EateryToJson import EateryToJson import json dfg = EateryToJson(ExternalEateries()) diff --git a/src/dfg/GoogleSheetsEateries.py b/api/dfg/GoogleSheetsEateries.py similarity index 95% rename from src/dfg/GoogleSheetsEateries.py rename to api/dfg/GoogleSheetsEateries.py index 4d7c9b7..ddb1da0 100644 --- a/src/dfg/GoogleSheetsEateries.py +++ b/api/dfg/GoogleSheetsEateries.py @@ -6,11 +6,11 @@ import pytz -from src.datatype.Cafe import Cafe -from src.datatype.CafeEvent import CafeEvent -from src.datatype.CafeMenu import CafeMenu -from src.datatype.MenuItem import MenuItem -from src.dfg.DfgNode import DfgNode +from api.datatype.Cafe import Cafe +from api.datatype.CafeEvent import CafeEvent +from api.datatype.CafeMenu import CafeMenu +from api.datatype.MenuItem import MenuItem +from api.dfg.DfgNode import DfgNode import os.path @@ -168,7 +168,7 @@ def description(self): if __name__ == "__main__": - from src.dfg.EateryToJson import EateryToJson + from api.dfg.EateryToJson import EateryToJson dfg = EateryToJson(GoogleSheetsEateries( spreadsheet_id="1ImfeTUA6I1Ub-aavgIW53Pf7EVB694f1294NPSCRd5c", diff --git a/src/dfg/InMemoryCache.py b/api/dfg/InMemoryCache.py similarity index 96% rename from src/dfg/InMemoryCache.py rename to api/dfg/InMemoryCache.py index 15b21f5..60846a7 100644 --- a/src/dfg/InMemoryCache.py +++ b/api/dfg/InMemoryCache.py @@ -1,6 +1,6 @@ import time -from src.dfg.DfgNode import DfgNode +from api.dfg.DfgNode import DfgNode class InMemoryCache(DfgNode): diff --git a/src/dfg/__init__.py b/api/dfg/__init__.py similarity index 100% rename from src/dfg/__init__.py rename to api/dfg/__init__.py diff --git a/api/dfg/__pycache__/Concat.cpython-38.pyc b/api/dfg/__pycache__/Concat.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..437e26797a2a0a1ab355fedf5b246cd78b60a632 GIT binary patch literal 949 zcmZ8f!EO^V5FM|*$tG>p5^>>%xaCmx1{Z{i8i`vtL={pbWNmFHUAoy#?G07YRvhS= z4?z4tzJtHmD<^(|6Eof#}BR(D6U`MA(VVYnw8qcc?Z0v7{#52k_PMm)jV zKbWs6rlA%(e1K_3cXStRS4X;sHqw3Ua!_~9i(Dn;n$?lIN;7Rut^zE$74k{XQ5_9f zu?i&OpTL@pEe$mcYT-;avmpj+uo0R+@8b(+tcz8V=Y_Q<&990~$D=~67A7xU{ARfv znNM+UZcj~88haNv=JYaKnbXTeU7K9T&Z=0?=5d4a`SPxgq)hX)l(K_uoQKLjA8+#8 zoFekJLa<2Rp}J?d%;BC_@DWDvVQ?=hUI`p3_{Lu1{!PE)6R=dg87{P4^lOpWxvM+Z zw~e*Nt+KKX8_sBAKep}BS%j#PER&KVM@e8J=(Cfc1++P_2b4gQdaelpfoy|WGY_U| z<(pCm&-){E{%2$Nplvqxn$YuUh94mqTl!B;<&&Ma>ro$Q eqKNz5@i1sg{Tsty+ug0BR}GHW#DJ&jkpBfTzr3aZ literal 0 HcmV?d00001 diff --git a/api/dfg/__pycache__/Concat.cpython-39.pyc b/api/dfg/__pycache__/Concat.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc1f481fafa8fd5f16ba6480405afe83d1d4bfd9 GIT binary patch literal 953 zcmZ8f!EO^V5FM|*$tG=8s466GoRD%TdxHx?MUBKQ93q4i30Yg)NtbSRQ+v~@Xe$o% z%m*NjT=E_K#a=n_3!IqoCSk)!KwVa#0bn)xO zVrZ`8+}xa)q%`(6Zp_JfwlpW_iMlkoj-6Gpp3dS1<+H_Y9Z8wyX(?p~+c*!EeL7s{ zH#tS*ZG>Ra;kT&nDK0a(;}v{_A$)jrCn{cviltBmKiF6H0&`dWijTq4oo2w$cG0gz zVrQ=IT;4R+8n?{KI&65Osr~4-M`sbHO0rB!iX0_@iJ;GpgBIB4#12>jPU`t41O&7V zX2m?1rj>6>9X$6B(fOZ|?W49C*=a&gs2P5SU}Wh}HI*N1y={+rPaCIfx+v2kKW_Cp zTC?9uj|M%WAW!B-%DOM*ywJ;x{Cz2}mPxkGh%9xbrB}4{x$Ge=^k9~TX$`qW;V}|s jPihn#@hFP8-yIEtrqsVN{I%`fI(pgQcts3&s`mL`3;n&# literal 0 HcmV?d00001 diff --git a/api/dfg/__pycache__/CornellDiningNow.cpython-39.pyc b/api/dfg/__pycache__/CornellDiningNow.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4e4d7ad3476ed314a7cfb220558db319698641b GIT binary patch literal 5116 zcmb7I&2JmW72nxiE|(NVeOQtu#ck8ZNy8*2755`);y89CH&J6VMI1Ni0`6kX8A-I4 zTzYnCTLf94u?rLdbZ8Gf~I$V%ZI+TV=1zav_{)&3dye7sI*Myf@GCRCubj z;4QFR3Kv^T-jXmrFyyo>KQd%lSr0Amw6v}pm6?CQN~0pu+>LfKYD36xY;D&gsp^o6 z8_jmJz5QVjhIq5rgDr)pOV4Yn-Gy8r`N}=jPN;#Dn~7>co*4~2YF-+tsN;H&sO?Da zqH}q?1sYqTwL!KW|K;#<2_^muDrI;=8lEYIXDM6dl%w)n=8CZbKYuBtCGAJH=cB|HytQyeV#TSm(+D>q^ks_?yNoMf9DLCDxZ&KO?81E6W+y&GPs>kDrxu zteZO=KYuuWUY=t8Db_E@1&m*mORQUzr{(e^!&{Ooh{S3-yB_Jb3d779_2_=@-JK-q z#P8HDX8Z~e9@xLJ+r{j; zW~>=~OA2!M9aBGmY(chHj6rVO7&!ghhH-OYkncPDPGauo`}qTLfJC_a>%Max^P_)m z#ds(_M;PG%r=B`-5+vO?&BxtFL&Y(}AV}S#4@>hcXoGE4aZ^{TyImD0acXU=L{H;W z()sm|KdE22dez_9tZ&vo@Nax_HMPkZY2nI)hUz4Mc-!BPrSF4*jCcu^AxyDgmc^=B5+z{^SInEX)mxbueKfLS>K8Fgmr;!&#Ds!KY#k*& zk4g$@O6!r)=h$u_V)u-J=nH+8kkdB~1fpr|o0-J1%+)pJ>Qm>vPV5Bvstd?8=Inn$V@kQUkV@lK?Zv>d_LK|<^wb#{z{tkWlWP%XJ3Md?jUqZ+tAx-S9)}A>qKz{H)$nWie**7U3kY5{EeL+NlZojJ_xF?GfN2aEy;gGA6#z&j^$M+#A1*Xa`PYo2tw^_m z1d#d>HL)RYlOe^7nhAg>NJA4^E~CVhnS>Z%=80q5rVH+LxH$hSIlY&^mREqBxy0Y3~sva`iO7qk=Hm*-dR) z2h{U3%*m-o+{tT$z1UX-678UZf-L^Aq^)NedvH0>=uN=%#naf-2tV*cHGWx_4Tml$poV~(K> zl1?p*hY-RuX{0_dncA634g`pV=>w#4eqaMRR$}+c`p2*=bH`gliM>PHul@!E0{XR8ZqKXna>yp<$C7I_T?`GLRl%2(Y^>}c`p$iC&(@3_!28}&| ziB0J4pu{ht8Yhu54q=osv=xC){#$Z;=f06^Kq&>L6tZrTN(josocaY`o(Y{d1^|uB z;qZog5A8z_2|m0JK|@fnkYgFqEGKA@1tyx}*MvvKtb2Ll=S}&59Rw1ZA6kynPV z={thW?@%(>y!Tw+j0fl$pc%_7+9fHwn8fICybU_JSsn;Jui_5!2R>_N_L`b#(p%;v zZDf}#{}3SbAz;{-sY4bI;@1-&lNK+8%{V)Qeu18&GwAcv;o0AHG#m4E>K-`}me8$@ zwQw^|ZtPs#MX>oX;+gg7kf^6nL3>)#+`W3dsKm{U(XA9FWz34a%T_bf_GZ!UJ*)p=8_ zPMKo9Y>K5hJV{1bTEugK{Zv^_9bATRIA^oH8T=!lio0U<@P9YjqSfe*<(=lH0N;Uo zO?6}V{~X>9y}7a0b-KGy;&_V_`fEf)be8azCfcs>8H)(%#6TLfNg2Z_c4lng$dqh2 zIn8_N8C__i^&>7JfO{`Irzg8Tb?JCX$I5IY7~<<#6v*W`4QJrENU-EPL<#gwC&3ec G*8c%KA>Z== literal 0 HcmV?d00001 diff --git a/api/dfg/__pycache__/DfgNode.cpython-38.pyc b/api/dfg/__pycache__/DfgNode.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63f6ad66c6d89df8c95aac557ff674f667d37c43 GIT binary patch literal 723 zcmZ`$%}&EG3{Ki^6xbvLufS!8krzN{h#_v{fRNBGQHir3txHF;F~$Xn?PYf74S0)O zc?52puv?0l085`;$8kP??sdJs!_c0x*W?A`8znz2#H59A`{*thFyJf1GUzf0u2>^f zc)no)W-Wv}M5kGo1M3FBAy~0)2q8omBd9olu!EnYDUo z23ZQCovGPao2e70lSy0O3!~>volLb|h$1v^)0uAGCTgS&2xpamUSE98dNf)1SW1l{ri$4wqO$lu z(dPOJI-1$;{z0}2+9^BCH%sg#-$5ztN4?Xi(iTiI)>8T%DaRSiQu?k*c{fYaqGD+P sTO(KBWTZcoYqaHN9BGV$IE=&mtkd9mEY^|V|8DZ}aY1-O-R}tY33zXnkF$y%Yp z^EC@FJ4U#Dbebg`SP}q-V8N0QLWnR%P=h+g8f@SjvES(Q2i+XB;@DFeQB!__a5UwV z&Dje+cgdE#yX3xc_Mr4+I?7Ee^pVq9-$$t(IA6cHr?_M`#c&<2*grui^PHod%#L#cN>~bf}N$b+H#;h z^9wj~+5f>WK;q&nC;kFbC1$+cvK6f*&)Ck`^L^iplFZM~5Ewsyd{3@&LVn?9dU&vT z1k>CBBZ;Ir>C-Ny!Hh+EI7 zd`tVV(E*qd(M@oFWcO)+p-4&J8O7F#E zx3ZZJ)XHX(?x;e>NtMNNs~4X`Xtmn2p%7V-)k0uugNO(X=>F1q;gismV%W*RD_G&@ zFwI*q`?n=3c)|nL<@a&4>Jujw1u| z272Luf8w1Jzed(z1-0?oFF~`08KkK)#(FZT6YDFjOT8X>PDa~&I;o0N)N)g6_!a^> z>m|BpY_PNIJXKEYu|XOrNSfri5V$k9z!1t9qn9W<4ueY|j>j2gG&sIUr1AALBB5Y7 zT{|EQ6sSi#L^og)Q3rcO9m4+rxt7zaSb|W4O+_xK&m{2T)L1MDX(QIw*rMZH*H2g2 zc-qM@E0sxg=309hPA7?%&ufgS*mgwMqb90YBz+}>oe`p6%0Z6nIU(K;l6?F{BNgjy zmSwJIG4C}rH_+TfOK;RL5VoDirrQTaJSq^Z%5e|fNSrtz#AA7!mO~4NNXW{@-fd{sN$o7vR_Uod z^9wkD#HIg(Uw}B}%89?g0WssWbgKf6Job1dJD=zc^TP$_ zAxv`*j39z$WI`iKanCX~aU#d=oy?thkw?jMB3$7e5#dSi(20EEZ<2272fR(Xw057w zBMED}pQ==iU&L7kUaLPE4{{+B%$gs7sHuJzHj+eCkcde~&<|8F;T*Y<+f<7@;fvN0 zfqH>hfVCysP3`wM^A^o{6xZC)0^kr3oJuMPI%PmE7Bq z7E0JG8^dfWH->StBb5l_A`Qi899}@%D`s`TIj~i79zaE7QPcgK7gJBu5(;^z1CgVM zFJYPt%orXZkR3RJ9k>;F3s+4^#Xi&bv`^l=IPgkZc_lk!`hMjI=LY%cRNg0$e79pG zE<|2K#GQ{+uIZGX@D8yx`~!ElQ#mO709f-95Z%;E!fMX9L3Hy>uK*Uk3}*kQL%RiK*`y{)cq39s9!NtG#Ml}L?!QTuUG0CyS*sl7s{s)XDH^xRmFP3`aO z;To-4##3qdaK^Df)DfxR5L7%K=F^g^T=7PhChj+mmAEmB__H8H0^m7E&)ft<7^Cc$ z@44(MZPQgdT^dlI{av8u^>YHDSU7Fxp~5K1m~Ik{j_tf-YOi$xj%{NtRRXxN{<0>7Me?6W)My zT7SZ6(xJwERAyBPiuYMuOEm?pwKq)rm5>RVTy8E*cA@LfK}Zr(K|&_Epg&Q;1fM&h z)3^#<>B*M#<(g~{`E4RRuxJT??wrzaU95@roP>c`2Oxn78pq#MU+Sv707S`QmWzae zk7Yw0`=`(~(leto5<$cRazXpbgOv_5j+XgQwP12V6k5I5?>*8|>0VNmWu=tN%EKxb zy}c@#6tb*!@8Nj7C!h36`E*O#@O#a$b&yZw)|a6G4?xVw2tvxpjQv8trzgCoBR1nHozb7^*JQaq zAj${h4!1EXybaT;t7rtgbsU8tj1#L=8}I08Gb*Vkd2PvP#5x)Q?j+9hC<W zdPW-l&M5Nw1AFWMhnN``%+jYeK!?m>-H(kUb6LRpHCR#@*d@Z++j*wzq$LgEemaFHawLzoP86Iy+*Rdjj3rZZ-AojfFP_* z1IAu@E{@(y&vU4c-(Sa@R}sHLG8PV3DPejkbuIH-p-uX&A$_P)&K*2S~~C zLs+`Htd|!3&IVRf85c5&OgoB-N=$OpgD84DiSuShVZtb^HuVMw6983pmK3r+tc1ce zR_~#B9|dNIBj?{EoV_`RN$WXYPTe$)N>_1Am9_g+uX zZZ`}(fB)NGgM862{y~kW9~+J9DEVJNLL+3c;ZKi6Vo$U#j`vMJTtZv$8$8# ziff7Mxw>x0^`zl7blr)YNy}^LdM$1z9j~M7ZakCBdb7ID;yG{b$3|EW8xM@IA)G_g zn-`bD=02By#yYKdAzAbmPi4iTm_IZQnYSdC#oXRyv2n?`r$G0tIZ zcbT$pr%{%oQrq0=Z)c%E)w&Vv(6_M}rBT{{JBVYnT;jej(jn?~s&CyBX+a&te5Vje zk0yQksiAQlCI1tMFi2G%51D87p|U?P&l0w9L`}Fme8pIS${w-M3hf7$R~HS@6s^6s zF165kWQDcRePH2O9rW3Fk9%z~gR%8TcGw7;T4ELwbLgKBTaP#$U2`w#aSP!Lc(dW0 z<}HF(%`+d)gMTSp(EO!vF<*=7Iy*9=PB#yVp`@CV4{00} z>_`D#+GEh-@l!{I%1=@9UxW0Gu~`@e+k=&Vvpcp5^T;Z#2@BbZF}6#7WQV+P_G(A& z#F+50qd8{jfLAA_KV-|qua)+uap%g|EzP3_W^0yi>69?QLvsT3+5Wz!o!!q7mk&8xon{++aTSRva*3lC7nnu zZGC)e>%;Ah_x!C5RNmRT>3^_s>uqI`LX{O~L8#2WD3lvQ)kTyDnz0bdE;7ul>ck2~ zTm;Gq2ADQfogVP&@5n6iwN>iM%0;}RtU&g2cdj)N}^ z`6A@M3f6DuLgwqeEKM^hMU?Jlak##j^@a)jBVXSb3^v8R^;F!yrj>HAUVW~89uLK} z&x77RcDNo4qV;g6zkVKPYl8#j`hE{S==-Z!Fux39FpIZXhtIRi++{A~^#1p%Gg^6O z{PEbXC11e6)6+-_c|DV6n?6)0<_(nm1CYWf5ieGV!a+lGMdJ%COY>|{R&rp&0C)pzt{Kkp_6gD3^Kt^j@S!n=+HF`lZ80H&N1ga5=yeISB3^3^c1`>XSr~|*s zy(SqpzY%A-2)mYA&;~e>@pnl`&1tTd^m|%VH8kpc4GS0gt!m9)mdP+mX-?(FSf?0< zLe=9e?N`+%da*b~v%KrbSD*ydtoG^$v{^?`)XU`>dgMBFTQKtw+O>$I0lcWuRtcVh zPF>P$l0Id2&GS|vP-$ezm#FqNkZ+^p&x078Glw;p1?rH=|6@6gs{KFyuhG|^G5$H& z+p>#E=HNK{5-jA3EKg*2^ZLcETq73=bG49>uqDr)*)pj$ZGN7}eNoQqrNwIBwtlZP@~X>)Wb!UxkC>5c)L_9x_(w^{>C`_aNZXuUw z$O=ez<{}VySEh&Zi_F)^_2B{oxQNKT=d^zv3!LwDryaRBbH1xx9eyg{HRmIjt6-=c89I_9DH0{*>o(HSK=CYE_M^p9X_FNmu>=+jz{0m>?b>B#(6l zrhRw0vM87;CyCNwA-ay@5|MFd7{^Ia^ma9Xltm|2W<&<*%WOF4+LH9IY%QehDj?UY zNpjDQa(|GVx7r3q9H8V2Acon3Rb1YI%{r*Ta!*DtKVyRnFpWJ=BfpN>endh0SyRwq z$yc-~$Yl{lUMxH_rqb$d(EcXz`z)i<3&>LD0>Aq~k>vu&E7rO__qIGb4 zxrwQCg1`;U)@vh}Yxi`i#{mK3L#p2fQL|N+I^FAewR{Isze347AO1+Rizp>aZpUU1a$on3 zvQ(^{bd6>=65OzR!8(L|66)5JH8?0nmA{d+d>>~(0?!a;qmihvS(~n0-T(BSp#Kwv zC|-=tYy3K<`5j75u4>d8Z1mEFz|)hR1lTk8aB?0h8eKGv{=eeeI(+N(x=cO?{<*bP zQ{Q@`L4n_|-=ja*24kb=p4q`ug4_5N!EKPPP`7ME^vT4)OcBQf0Px!BtLiFu>)lI_*c`cr&OwYM|=kzv4 zM#>g`-+%w_#-FDx>pwKu|MM{T6>jwy4YxRptssB0fSIu!*yia34xV=GcD%r2IgcOs zpgD1&Qw)lR=EkK?IVhXH7gsvfplbSlJkl8rMoqsEk9BH6&GcD3ZsZr^iOys&X=tT* zsxuu-n|?W-=^P1;n0_Tb+L;YzO}`o+>l_b`GwVZ(kMPm^79SP0O(!@Zp5tS;ZTSfM zs>O4ilfg-5iId_4ALkQWY?B4^;(5VVPl@?Wi%;^YEqjweBD-eQryoKG)^eTc;wRlK zN|Q#6zI(CJ5_p#AdHJqLGGd^?$0F%bpPpAT(Luj(snuRiIq?@h6?b|<5qIUH6dVhq zMyxQbT}f2dh-1Mobh{tK=^AL>tzHy!bV|R@vhHOmQ`w~5?Em=~{0g`F7aC!aeW1HJ z;p7*0zje9C{rg_v@&Yg3w*pW2qQFbv+Pusw_uZff&XOq4TD%Gx>CFu)pg6OZs2chh z=rukL`UrTdVsv#3+!LTeSD@C!IQS>{G$=Ft$bALUHnJ7HUrtULZ#J4Mf`>+&AvGx~c7N2j zabLu(PM{gEffZT<8!a0+XzhWE7SVyPWXp9|j)O~1P&0`}*K*0`HAa5j)2j0yi z0{1^Hc?6F~V)2F&QY|*qBuS+dQL>W8eDPA+>~-KuY7xG3N!(pb#M)V-srAMDarRc+ z6K8KVnotg3Y;>bV-fAx%ruv2Mx-N!cltft=9>Ysj5sg*w8D0J74qda$-(bFZ4GuP*62M#oBFYDs2Yt()1rd^R3WowrlMZ0m@;M#9=LFan3 zi9iioGVO#kTQA5NY)`ul*;d-Wy+%(-->z9pj}XhA-|5Pe;kq$O-W(-v=&gwm4#Sr* zQdKk-bL}b{wTo=Y{#o7SYwEUqk%ahCWIdTQ$-Q=e)F;R`?)CvXP@_>BS{tE=P$jkI zH12Lu&jZ~DJ?|A}Eu(J6i?bF~UfS}Zw(_!elSW7AaPjv;qY5LhaC z1~c+3O}p^i9^%8H!JM&7&XXIcZQvm^A*|#GF-;mKTDH%Iwy~j6;Q_PAlUQ^Pw<051 z%!VJAnalj2T&KL#v;W(F;ZV;$;3>9z6(S_r_bCI7g0N?x>xcr*+H3QFo{q-D0&~OavYTIVjGg0&R{ZZ4ul#O$r7X}V5ZaIiLe>kyiZ4?HD;pDcp zIt97Y1NM!@OWz?+Y}vyZ%oYd5;S{g5E$qE?(@NfTt?bAHwm#3S4P^KJsqE;$!kX%! z{EhXULpii<-L!n`b42h)`K$7^o#&FZL}*YMlo0bTZd4KPvxDk3+ZY*)aI422F!}1B zGCVdIq3?gd?qCFW9a-+bxq?in&Mz*mt*tHOz5z5-3(d5%*yv>|i~A5?EfDypNxUw{ z$Z7;m@MyOS&(>@$@N=gM?7MG(T-ldP`tI`=&R?wC!N@*wSGe|MBWVlm-)#U+Ro^~8 zUq7zxR6!=C5ntSk6r3A1l_g7Pf>{GHw6Ro1edLoK@H=cpvCtlbL_8>nd))|GEEtiZ zC52kaQ%+FW8A^gsNxl^vHNu*4ga`_izkbo}l3Mr3cf%ev9lnGtP zQiF4{LQ<;xK1s>9iO*?^%%mNS-H2unxt737dtDhNnJ#r3Qi)KeYjO_c`e+_%6baB_ z6IiRXN7_??+d}1{J(Jsl0_`Pgioi39CS+5+INNzAHYu0P+aXwcCgu-D?rWHT54WOi zTCR_%9R-?}Sq*KKRqauB#aoMfeH8#PH*%%?V%i!z3{_~Ojvq%Fp{t&Nx zHqt5clSvE|{{klby z)E!E9Nt#6D9URcD$533!EX<+~DpBCR9zjxkGOU-h6N^L_iJ6$RYnF|W`$Z~krLxn= zbonz10#lUga_%9-EM3}z!Rd*;H-re!TTvo--OGK)==OKu49ZV7gA~5AkAr^jJ~-RB z75SgVs>rG~n}P=v;Q^ztIE`(x_mBO#L#@5%!gX60XQw#{|$r88p5^% z2E+sYswra!^o9<%w_$f+a6SUMW8|6{s%gC2_J-F#k-4iL>OoH~b3d_p;fMu`7PsIN z*HF=9{`y~moj$PhL{>1^3A`oTyqROB{}s+s1~t(m)T9y8cK_7rFP6@}^W}S{t)I_F z`C$I^7ZIM+tExM zwC!~)+Q4nfXbU!VVNF;Smu=$Req8iu)Ia%uEKA#hX~#`d+!A%_#4}0^YJNtcXUOt*mR8-UGZ? zfhbe)AengP-F3$Rrld2PB;Wvn+*q!n(Y1qnJ*b)2!Sw*QqUyr3T~v{#3V&So`lk=o z#o>h!#hLsgnx{&Zr_?|NP44OhSh-8XbxpBC&cDru{l;?MTpPEut&1WlO z{zekrn?HTy`o;SEUfj>8Ei<nCb=O-1V%V2QzWYKj0fsl~NW&s?FWCu=8eJ+S?Osu%{4hXZ?d(zX37d z;#LG?R-w%LuN;s`P9&>AEEOJ)$w7d%VF3_c@KqssHz;Hh6*W)3*YxP4P4b|Qq6(wvnUmfRwn2EH{14S?u~Ms{ivO{C`smEeq|GKO zHk&EsNeAE9X+%jFA_}WU#p~D8k8p(RpE-A>(-PNjTzKosxf{36HX3)^S8l%f`qkDi zE`Rp^TkpKlI``T;Z!BNEcJWjGW)t=pr@at-LXsaP^j}T=h5R$=#4q2W=J#l{pRIRs z>dBQ66J;Dl=>mMcff=!I5y#E^d^7V5{W6`0j6(JFGm{_VjAL|nV2y1Fr~!x+QPnCc&Cuf z=R=wf4O1N5TA*q#uT6K>V<&liy1*k;hNwi>^QvKy-qg|n|=Oc;-&a*?$*Dg&{I@iQe{Y2)h<%Sm|y$*PVGg`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10UKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 sXQ$?+=oc3y>!+lp>&M4u=4F<|$LkeT-r}&y%}*)KNwou6`5A~A0D-+PB>(^b literal 0 HcmV?d00001 diff --git a/api/dfg/__pycache__/__init__.cpython-39.pyc b/api/dfg/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01158117b3d6b8174e71d01bd84cb547cc0d75d3 GIT binary patch literal 180 zcmYe~<>g`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10UKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 sXQ$?+=oc3y>!+lp>&M4u=4F<|$LkeT-r}&y%}*)KNwou6`5A~A0E#UyF#rGn literal 0 HcmV?d00001 diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py new file mode 100644 index 0000000..c6f14d5 --- /dev/null +++ b/api/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0 on 2021-12-23 19:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='TransactionHistory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('canonical_date', models.DateField()), + ('start_timestamp', models.TimeField()), + ('end_timestamp', models.TimeField()), + ('transaction_count', models.IntegerField()), + ], + ), + ] diff --git a/src/migrations/__init__.py b/api/migrations/__init__.py similarity index 100% rename from src/migrations/__init__.py rename to api/migrations/__init__.py diff --git a/api/migrations/__pycache__/0001_initial.cpython-39.pyc b/api/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e30283f452f34d45038d9ce13a22f9a69ad38c3 GIT binary patch literal 844 zcmYjPJ#*AB5S3-;K7E(KP*Fj{%oT}4&oBwO38Y9GLgPlG^KKknY}t`~fun^s{{c$= z2Q@uvsqz<604v`?B8^sWCC%>Jx00jLfYF%!_%45P#(sI{{)ougqh+6w0R}=|NJp?qM7Lc3_#eYl_wdw6ZDr>9zEQTR%+hfMhmIdjCy|TgqNzk?P&No| zSQ}MJvlJJ&bbX6P$wGZZ7hj>7RThPoCA!Gl8=%T20jVKBhyr!qoV#%*b){7%3jq|* zZQ4efMl`BKLS^knsNtUbpt<8FvTC852{$ptb)7SX1=y3x*!4u4RR}i_g5+KlNH`Ya zav=-PguJfOsv)i}2COLu>P(?E)OzNI2W@f3zELLhC-Ezl?^D@_K8NccoJ+HYQ)0b_ zr$l-M<0IW*j_Wsrs>XHqzcKTK;R@tIN-PmNmuBUjF zXneoz#Wu@iwQbLe1#Zt|c0t4wS*zskeg`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10DKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 zXQ$?+=oc3y>*r>s7bTWt=I0gb$H!;pWtPOp>lIYq;;_lhPbtkwwFBAw8HgDG0gEz3 literal 0 HcmV?d00001 diff --git a/src/models.py b/api/models.py similarity index 52% rename from src/models.py rename to api/models.py index 71a8362..d49766e 100644 --- a/src/models.py +++ b/api/models.py @@ -1,3 +1,3 @@ from django.db import models -# Create your models here. +# Create your models here. \ No newline at end of file diff --git a/src/tests.py b/api/tests.py similarity index 100% rename from src/tests.py rename to api/tests.py diff --git a/src/urls.py b/api/urls.py similarity index 100% rename from src/urls.py rename to api/urls.py diff --git a/src/views.py b/api/views.py similarity index 69% rename from src/views.py rename to api/views.py index bf39652..78304a1 100644 --- a/src/views.py +++ b/api/views.py @@ -3,13 +3,13 @@ import pytz from django.http import JsonResponse -from src.dfg.Concat import Concat -from src.dfg.CornellDiningNow import CornellDiningNow -from src.dfg.DictResponseWrapper import DictResponseWrapper -from src.dfg.EateryGroupByType import EateryGroupByType -from src.dfg.EateryToJson import EateryToJson -from src.dfg.ExternalEateries import ExternalEateries -from src.dfg.GoogleSheetsEateries import GoogleSheetsEateries +from api.dfg.Concat import Concat +from api.dfg.CornellDiningNow import CornellDiningNow +from api.dfg.DictResponseWrapper import DictResponseWrapper +from api.dfg.EateryGroupByType import EateryGroupByType +from api.dfg.EateryToJson import EateryToJson +from api.dfg.ExternalEateries import ExternalEateries +from api.dfg.GoogleSheetsEateries import GoogleSheetsEateries dataflow_graph = DictResponseWrapper( EateryToJson( @@ -33,7 +33,6 @@ def index(request): ) return JsonResponse(result) - def google_sheets_eateries(request): dfg = DictResponseWrapper( EateryToJson( @@ -49,5 +48,4 @@ def google_sheets_eateries(request): end=date.today() + timedelta(days=7) ) - return JsonResponse(result) - + return JsonResponse(result) \ No newline at end of file diff --git a/eatery_blue_backend/settings.py b/eatery_blue_backend/settings.py index d13244a..68322a3 100644 --- a/eatery_blue_backend/settings.py +++ b/eatery_blue_backend/settings.py @@ -37,6 +37,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "transactions" ] MIDDLEWARE = [ diff --git a/eatery_blue_backend/urls.py b/eatery_blue_backend/urls.py index a6919c8..ea8f685 100644 --- a/eatery_blue_backend/urls.py +++ b/eatery_blue_backend/urls.py @@ -17,6 +17,7 @@ from django.urls import include, path urlpatterns = [ - path("", include("src.urls")), + path("api", include("api.urls")), + path("", include("transactions.urls")), path("admin/", admin.site.urls), ] diff --git a/transactions/__init__.py b/transactions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/transactions/__pycache__/__init__.cpython-39.pyc b/transactions/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db4d1719d8265764a5f061bc66181a3ed0961740 GIT binary patch literal 185 zcmYe~<>g`kf~#F85<&E15P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;x_qerR!OQL%n< zeqLUFQBi7UUPgXSioQ#Ja%paAUP-aOV?lvSYMFjsYPoJ|Vo7RIr9OnxP0A@v)lEuF y&Q8rs(Jv`V%qvbzF3HT#E7p&X&&Y^M2L8e6vz>$`@@M zm>K5rn2|v+VubtUCgOnGY-`cJyDkaq*AssdM`myXpz z;!el8MrVr5^aWRDe6p{+w_;<#SV0e{je!F9x>O?3c~b#8E0%Sgz)2vSBajYED}EMb kWdl(r^CO@V&PcQ}cZEhPzUy(_cEf3^8J4I`R?R+JRm4vZ=doV?Y9rQ5@ImJ=Z;YU0jfz(6H3V?fdJSm0jB5+ zO9BY5NYwj56p3haFfWR8Yu4E&^_i|v3}*P;Au2@@3Iw}p!XQXOV8{|Slj~9%C(|mk z#TLUa|=ql_$=GZ>&jkaN<%ZMIxWkn*Yt%jGFL)010k@Z*4`fp@lZ>> zuS`I#WvweAkw>g|M4_*Jkka2}r+yQD<5cN*YK^gFsWNkGHSmQ^>s*=2aa?wx9=TCZ zQza`^Zh32_D_yJUN~ZV90PhaSWl^}e*p>``@i%r}^=U|VnPtS9;eKC_ECWA`$NrDi TdPB@L{gZaTQ-53Mu_N{eoh5yj literal 0 HcmV?d00001 diff --git a/transactions/__pycache__/models.cpython-39.pyc b/transactions/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f78c2f7b16f25659917b6057226cf3083795b8f0 GIT binary patch literal 595 zcmYjPy^hmB5Z<+&!w$}!;1TFj4 zY41vBJ}4JtpaezjPR!820mkx;3Ao0wvv?2=bIaOxCj4gZl;{WZ?|t>oqw`g5jj_&Q zV>VVpwX}8Dq6xlwzS}MFQ)Te;0%;JpuSU06=?)hwRevD%>WIWw<0Z~_d)F_2LhAdY x=e*?k@C^A&T517XW!Cl_R^vt{Z~G$-<-?3)bVeOw{&d{uGx~Ly@0VC+{{e*QqhA03 literal 0 HcmV?d00001 diff --git a/transactions/__pycache__/urls.cpython-39.pyc b/transactions/__pycache__/urls.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dff009d2b3342e8d879045f39e2808738f5a2439 GIT binary patch literal 405 zcmZ{gJ5B>J5Qgo2WEb*~kZ344K#If{h!9ArX%NjeE5|b=o9x;v+nXqdpyCu<1aXFK zsd5Dh#8@rA39> zm)=V4r1F)~uD_L?H~*HyJS`jSuN$Lw(*ALp^+VW7y*BgC*3L$>wP!Y= zWYZv7xENB6OnJ20y)!!ug&^OBcy?%=P)2KGEmnGCY7k4Kx`vE8@zAzQ+zX9|8A|Fu qioY~l)g8`OQtil!IDd}_KIZMQ&lin>uEx9Y6D}tiMWcAaCqKV2+;zJE literal 0 HcmV?d00001 diff --git a/transactions/__pycache__/views.cpython-39.pyc b/transactions/__pycache__/views.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af826d932c3b4c47288ef8f0fbcb61a0833e2516 GIT binary patch literal 1129 zcmZ8gy>c5j5C-m_qU5n-IZY-rnM@iMe`E(l=m zu++8`t|D6bvVwPZRA09H3nDOEb_`}@Yg1|G(^`+2aqN@*4<8RVqTca2BS-QL_nbC? z8^ICtI6EC+J2emTfD=vu7>b45gS>dM#7*5rA8o|oMv*G0o0|+l@X<< zJrt(VuGl|3Ih`K8crkl*G@YEB&R(7zA5Dvo{}&y9`+RnM^yBJbS=XxC9+01tGTLM; z1k`TTdj=%}e)h|z-5WP;f*^ zh|<-ag0{v>ul=-aoH+w0<}(CA>Y3e_#U3rO`(zl8c*1w-%lHGH1t0M`(b*exE) z^HRStBTIyxXq-i_l(v5x_9g0I7LV^JjI6^(xP}0Aw2RM4%C~RDj_wQa-+1>b@Clua lkG%0R>=4{zZU~xd#I4QfUN>SFF{e*lAOeGB&U+xq6CTsy`-g$cF7IN zjjM%xavq|G9FpGpCO!25_S#ckp+$=hS5n|ZDGYaJNDjaG;RtVSH4GSk{PH{g+ck_o zwQzITARGWm1QRicnHZ^wOjGk#Vx=~+E8b3=)J1N^JBgRpP_5$K#7}+XSGAdxqE=cYh>m9`S<$8kJa%8M zwP<^+!1b6-pWr0v;R}jtuwTuvESg#jo>InQT5LV$35h8j)Y@YEFo`1?^f;dfPk1hA zai@nx5_D-ar(&f-em}psh-pHK)?+~_3;F_2IYa(L#7Pq8X>sc`iCOS0J_l3UYzvI= zWOE4s0BL>WZSX}wR)Pr?qH?91OK^~9K^ISHB8#n!D}nX}c_&=Xs3>+;F3SKbfTw13 z4Y+kesp4#n)fqh>92Jcbm+N)p_n^E4M5egYKOP3-j7Bj|f}>ZIDdde}nb1Yid;~?u z7J&HC1bI&mA3XT_tD-rGdR(M&G{0Y=TH)?ub`cjJ+^9rYzqRpeM5o%aGX`(;Hi6^m zOs(@&Y(rgE=>^2&JXW;0)e{`j&8DjWq2}qFGC9-nmPM-@PlFL95F5b&Z7$V97WDyu zT}OIz?Eps-`2#LQ4A1Mmrz^@r^|RseBpeYD+|kWOb|TIg1c1hD236ka z^C(ZjrtBPMS)ab@FnYO1bu}zHtIytflGD9&48_h!2U5i(j#LbdN!wcY57|lKX@yWP z&{p8$jeb8{l(kH7oz6PIkO54F>6>2wIY9d7n3ie%+i)%5f%~WU?EhNS{@TnCdYB)E z45u^<%SITcoaBk-TVePz$H{6%d;%9$uHTsMX`OCunxyWQzFEn`-B$T2c>zt8?^o#K z^L?DF**+MD_a^k#@}mzM1l`XzEnDcnjdFXvM$R`!9IbAy)ib5CDz&S!@N^63X_fiv c{u>x|wuA;+`roWv`&PF;sVksme)w7c0h=cLkN^Mx literal 0 HcmV?d00001 diff --git a/transactions/controllers/mock_vendor_controller.py b/transactions/controllers/mock_vendor_controller.py new file mode 100644 index 0000000..d9c878a --- /dev/null +++ b/transactions/controllers/mock_vendor_controller.py @@ -0,0 +1,26 @@ +from django.contrib.auth.models import User +from django.core.paginator import Paginator +from django.db.models import Q +from django.http import JsonResponse +from datetime import datetime +from random import randrange + +class MockVendorController: + def process(self): + now = datetime.now() + recent_timestamp = datetime(now.year, now.month, now.day, now.hour, now.minute - now.minute%5, 0, 0) + places = ['Bear Necessities', 'North Star Marketplace', "Jansen's Market", 'StockingHallCafe', 'Marthas', 'Cafe Jennie', "Goldie's Cafe", 'Alice Cook House', 'Carl Becker House', 'Duffield', 'Green Dragon', 'Trillium', 'Olin Libe Cafe', "Carol's Cafe", 'Carols Cafe', 'Statler Terrace', 'Bus Stop Bagels', 'Stocking Hall', 'Kosher', 'Jansens at Bethe House', 'Keeton House', 'RPME', 'Rose House', 'Catering', 'DIN Special Event', 'Risley', "Franny's FT", 'HA3350', "McCormick's", 'Statler Banfi', "Statler Banfi's", 'Statler Regent', 'Sage', 'Straight Market', 'Concession Suite', 'Crossings Cafe', 'Okenshields', 'Big Red Barn', 'Rustys', 'Mann Cafe', 'Statler Macs', 'Morrison'] + units = [] + for place in places: + crowd_count = randrange(6) + if crowd_count > 0: + units.append({ + "UNIT_NAME": place, + "CROWD_COUNT": crowd_count + }) + + return JsonResponse({ + "TIMESTAMP": recent_timestamp.strftime('%Y-%m-%d %I:%M:%S %p'), + "LOOK_BACK_MINUTES": 5, + "UNITS": units + }) diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py new file mode 100644 index 0000000..e69de29 diff --git a/transactions/migrations/0001_initial.py b/transactions/migrations/0001_initial.py new file mode 100644 index 0000000..5835155 --- /dev/null +++ b/transactions/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0 on 2021-12-26 15:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='TransactionHistory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('canonical_date', models.DateField()), + ('start_timestamp', models.TimeField()), + ('end_timestamp', models.TimeField()), + ('transaction_count', models.IntegerField()), + ], + ), + ] diff --git a/transactions/migrations/__init__.py b/transactions/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/transactions/migrations/__pycache__/0001_initial.cpython-39.pyc b/transactions/migrations/__pycache__/0001_initial.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04dc757209a5657963a081c46b5bcb54d97e58ec GIT binary patch literal 853 zcmYjPy>iqr5SC=;etf=5V5q2|Vdjd&p=X$c+yqi24WV(P(ODZu7h5)xFL1O_a_;~o zZ^A2}mMX&=qySdFgPb&4eJg2pzx`HXIE)C6U!T8=AB>Qn-q{=>ItRGy6Eu=YT9A^a zv}7rx!1E(c<%0>8=CzEmLVli(jo*57g zNH?gBDur3_D_FR`1*1fvK7fm_!AvU)T#FK1=bI&UuBC=Jo)>^P z=KSqk6rKrsU8PlnTwN@o#vGtC1=e8ei5ncX#hv;_nZTdKFI0YrWgq(-u77kX%o+XLe<`Kqg&25>yLA`8}Ra#fZfYO&$A(Kg!%}daYO%CgN z0@sO#ce`F}vq)Ck?zEW0?o?z~NIcn4#3pwiE!p4Sf6CWY?$rz9e{ctveTb&VLKcQ0 zW$XbR(P%Yq+3)aA`|b?;+?O+<^J-5{TUA;i+RM8VR_HOtbG>IPp`Uxbte0^o_zQf4 B`g`kf~#F85<&E15P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;x_!erR!OQL%n< zeqLUFQBi7UUPgXSioQ#Ja%paAUP-aOV?lvSYMFjsYPoJ|Vo7RIr9OnxP0A@v)lEuF z&Q8rs(Jv`V%qvbzF3HT#E7s4=OfO1=FyrGh^D;}~ Date: Sun, 26 Dec 2021 12:34:15 -0500 Subject: [PATCH 004/305] Create TransactionHistory objects in db --- .gitignore | 45 +++++++- .../__pycache__/__init__.cpython-39.pyc | Bin 185 -> 0 bytes transactions/__pycache__/admin.cpython-39.pyc | Bin 226 -> 0 bytes transactions/__pycache__/apps.cpython-39.pyc | Bin 474 -> 0 bytes .../__pycache__/models.cpython-39.pyc | Bin 595 -> 0 bytes transactions/__pycache__/urls.cpython-39.pyc | Bin 405 -> 0 bytes transactions/__pycache__/views.cpython-39.pyc | Bin 1129 -> 0 bytes .../update_transactions_controller.py | 96 ++++++++++++++++++ transactions/models.py | 8 +- transactions/views.py | 11 +- 10 files changed, 151 insertions(+), 9 deletions(-) delete mode 100644 transactions/__pycache__/__init__.cpython-39.pyc delete mode 100644 transactions/__pycache__/admin.cpython-39.pyc delete mode 100644 transactions/__pycache__/apps.cpython-39.pyc delete mode 100644 transactions/__pycache__/models.cpython-39.pyc delete mode 100644 transactions/__pycache__/urls.cpython-39.pyc delete mode 100644 transactions/__pycache__/views.cpython-39.pyc diff --git a/.gitignore b/.gitignore index 0994477..f4671ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,51 @@ env/ .env +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class -eatery_blue_backend/__pycache__ +# C extensions +*.so +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Django stuff: +*.log +local_settings.py db.sqlite3 +db.sqlite3-journal \ No newline at end of file diff --git a/transactions/__pycache__/__init__.cpython-39.pyc b/transactions/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index db4d1719d8265764a5f061bc66181a3ed0961740..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmYe~<>g`kf~#F85<&E15P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;x_qerR!OQL%n< zeqLUFQBi7UUPgXSioQ#Ja%paAUP-aOV?lvSYMFjsYPoJ|Vo7RIr9OnxP0A@v)lEuF y&Q8rs(Jv`V%qvbzF3HT#E7p&X&&Y^M2L8e6vz>$`@@M zm>K5rn2|v+VubtUCgOnGY-`cJyDkaq*AssdM`myXpz z;!el8MrVr5^aWRDe6p{+w_;<#SV0e{je!F9x>O?3c~b#8E0%Sgz)2vSBajYED}EMb kWdl(r^CO@V&PcQ}cZEhPzUy(_cEf3^8J4I`R?R+JRm4vZ=doV?Y9rQ5@ImJ=Z;YU0jfz(6H3V?fdJSm0jB5+ zO9BY5NYwj56p3haFfWR8Yu4E&^_i|v3}*P;Au2@@3Iw}p!XQXOV8{|Slj~9%C(|mk z#TLUa|=ql_$=GZ>&jkaN<%ZMIxWkn*Yt%jGFL)010k@Z*4`fp@lZ>> zuS`I#WvweAkw>g|M4_*Jkka2}r+yQD<5cN*YK^gFsWNkGHSmQ^>s*=2aa?wx9=TCZ zQza`^Zh32_D_yJUN~ZV90PhaSWl^}e*p>``@i%r}^=U|VnPtS9;eKC_ECWA`$NrDi TdPB@L{gZaTQ-53Mu_N{eoh5yj diff --git a/transactions/__pycache__/models.cpython-39.pyc b/transactions/__pycache__/models.cpython-39.pyc deleted file mode 100644 index f78c2f7b16f25659917b6057226cf3083795b8f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 595 zcmYjPy^hmB5Z<+&!w$}!;1TFj4 zY41vBJ}4JtpaezjPR!820mkx;3Ao0wvv?2=bIaOxCj4gZl;{WZ?|t>oqw`g5jj_&Q zV>VVpwX}8Dq6xlwzS}MFQ)Te;0%;JpuSU06=?)hwRevD%>WIWw<0Z~_d)F_2LhAdY x=e*?k@C^A&T517XW!Cl_R^vt{Z~G$-<-?3)bVeOw{&d{uGx~Ly@0VC+{{e*QqhA03 diff --git a/transactions/__pycache__/urls.cpython-39.pyc b/transactions/__pycache__/urls.cpython-39.pyc deleted file mode 100644 index dff009d2b3342e8d879045f39e2808738f5a2439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 405 zcmZ{gJ5B>J5Qgo2WEb*~kZ344K#If{h!9ArX%NjeE5|b=o9x;v+nXqdpyCu<1aXFK zsd5Dh#8@rA39> zm)=V4r1F)~uD_L?H~*HyJS`jSuN$Lw(*ALp^+VW7y*BgC*3L$>wP!Y= zWYZv7xENB6OnJ20y)!!ug&^OBcy?%=P)2KGEmnGCY7k4Kx`vE8@zAzQ+zX9|8A|Fu qioY~l)g8`OQtil!IDd}_KIZMQ&lin>uEx9Y6D}tiMWcAaCqKV2+;zJE diff --git a/transactions/__pycache__/views.cpython-39.pyc b/transactions/__pycache__/views.cpython-39.pyc deleted file mode 100644 index af826d932c3b4c47288ef8f0fbcb61a0833e2516..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1129 zcmZ8gy>c5j5C-m_qU5n-IZY-rnM@iMe`E(l=m zu++8`t|D6bvVwPZRA09H3nDOEb_`}@Yg1|G(^`+2aqN@*4<8RVqTca2BS-QL_nbC? z8^ICtI6EC+J2emTfD=vu7>b45gS>dM#7*5rA8o|oMv*G0o0|+l@X<< zJrt(VuGl|3Ih`K8crkl*G@YEB&R(7zA5Dvo{}&y9`+RnM^yBJbS=XxC9+01tGTLM; z1k`TTdj=%}e)h|z-5WP;f*^ zh|<-ag0{v>ul=-aoH+w0<}(CA>Y3e_#U3rO`(zl8c*1w-%lHGH1t0M`(b*exE) z^HRStBTIyxXq-i_l(v5x_9g0I7LV^JjI6^(xP}0Aw2RM4%C~RDj_wQa-+1>b@Clua lkG%0R>=4{z Date: Sun, 26 Dec 2021 12:34:43 -0500 Subject: [PATCH 005/305] Remove pycache --- .../mock_vendor_controller.cpython-39.pyc | Bin 1743 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 transactions/controllers/__pycache__/mock_vendor_controller.cpython-39.pyc diff --git a/transactions/controllers/__pycache__/mock_vendor_controller.cpython-39.pyc b/transactions/controllers/__pycache__/mock_vendor_controller.cpython-39.pyc deleted file mode 100644 index ee0c6ec07df5d190e76886fecfc8cb249a76f8fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1743 zcmZuy&2Aev5Z-^Sew;W!>ZU~xd#I4QfUN>SFF{e*lAOeGB&U+xq6CTsy`-g$cF7IN zjjM%xavq|G9FpGpCO!25_S#ckp+$=hS5n|ZDGYaJNDjaG;RtVSH4GSk{PH{g+ck_o zwQzITARGWm1QRicnHZ^wOjGk#Vx=~+E8b3=)J1N^JBgRpP_5$K#7}+XSGAdxqE=cYh>m9`S<$8kJa%8M zwP<^+!1b6-pWr0v;R}jtuwTuvESg#jo>InQT5LV$35h8j)Y@YEFo`1?^f;dfPk1hA zai@nx5_D-ar(&f-em}psh-pHK)?+~_3;F_2IYa(L#7Pq8X>sc`iCOS0J_l3UYzvI= zWOE4s0BL>WZSX}wR)Pr?qH?91OK^~9K^ISHB8#n!D}nX}c_&=Xs3>+;F3SKbfTw13 z4Y+kesp4#n)fqh>92Jcbm+N)p_n^E4M5egYKOP3-j7Bj|f}>ZIDdde}nb1Yid;~?u z7J&HC1bI&mA3XT_tD-rGdR(M&G{0Y=TH)?ub`cjJ+^9rYzqRpeM5o%aGX`(;Hi6^m zOs(@&Y(rgE=>^2&JXW;0)e{`j&8DjWq2}qFGC9-nmPM-@PlFL95F5b&Z7$V97WDyu zT}OIz?Eps-`2#LQ4A1Mmrz^@r^|RseBpeYD+|kWOb|TIg1c1hD236ka z^C(ZjrtBPMS)ab@FnYO1bu}zHtIytflGD9&48_h!2U5i(j#LbdN!wcY57|lKX@yWP z&{p8$jeb8{l(kH7oz6PIkO54F>6>2wIY9d7n3ie%+i)%5f%~WU?EhNS{@TnCdYB)E z45u^<%SITcoaBk-TVePz$H{6%d;%9$uHTsMX`OCunxyWQzFEn`-B$T2c>zt8?^o#K z^L?DF**+MD_a^k#@}mzM1l`XzEnDcnjdFXvM$R`!9IbAy)ib5CDz&S!@N^63X_fiv c{u>x|wuA;+`roWv`&PF;sVksme)w7c0h=cLkN^Mx From e3688ccf5a8f19673e320a3a7a198d5259132ef6 Mon Sep 17 00:00:00 2001 From: connor-momentranks Date: Sun, 26 Dec 2021 12:40:36 -0500 Subject: [PATCH 006/305] Ensure correct git account --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f4671ae..ff8b01b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ env/ .env # Byte-compiled / optimized / DLL files -__pycache__/ +__pycache__/ *.py[cod] *$py.class From 8ef55fcf6c8a2f78eee0ff29f9d9f0278452aefa Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Sun, 26 Dec 2021 12:50:11 -0500 Subject: [PATCH 007/305] Test correct git account #2 --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ff8b01b..513b7e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -env/ +env/ .env # Byte-compiled / optimized / DLL files From 935d7f36233b2cdf47db1596c4d76ee921f4936b Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Sun, 26 Dec 2021 14:21:02 -0500 Subject: [PATCH 008/305] Finish wait time data ingestion --- transactions/admin.py | 3 ++ .../controllers/mock_vendor_controller.py | 6 ++-- .../update_transactions_controller.py | 36 ++++++++++++++++--- ...p_transactionhistory_timestamp_and_more.py | 30 ++++++++++++++++ transactions/models.py | 2 +- transactions/views.py | 11 +++--- 6 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 transactions/migrations/0002_rename_end_timestamp_transactionhistory_timestamp_and_more.py diff --git a/transactions/admin.py b/transactions/admin.py index 8c38f3f..784107d 100644 --- a/transactions/admin.py +++ b/transactions/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin +from .models import TransactionHistory # Register your models here. + +admin.site.register(TransactionHistory) \ No newline at end of file diff --git a/transactions/controllers/mock_vendor_controller.py b/transactions/controllers/mock_vendor_controller.py index d9c878a..e943dcf 100644 --- a/transactions/controllers/mock_vendor_controller.py +++ b/transactions/controllers/mock_vendor_controller.py @@ -3,13 +3,15 @@ from django.db.models import Q from django.http import JsonResponse from datetime import datetime +from pytz import timezone from random import randrange class MockVendorController: def process(self): - now = datetime.now() + tz = timezone("EST") + now = datetime.now(tz) recent_timestamp = datetime(now.year, now.month, now.day, now.hour, now.minute - now.minute%5, 0, 0) - places = ['Bear Necessities', 'North Star Marketplace', "Jansen's Market", 'StockingHallCafe', 'Marthas', 'Cafe Jennie', "Goldie's Cafe", 'Alice Cook House', 'Carl Becker House', 'Duffield', 'Green Dragon', 'Trillium', 'Olin Libe Cafe', "Carol's Cafe", 'Carols Cafe', 'Statler Terrace', 'Bus Stop Bagels', 'Stocking Hall', 'Kosher', 'Jansens at Bethe House', 'Keeton House', 'RPME', 'Rose House', 'Catering', 'DIN Special Event', 'Risley', "Franny's FT", 'HA3350', "McCormick's", 'Statler Banfi', "Statler Banfi's", 'Statler Regent', 'Sage', 'Straight Market', 'Concession Suite', 'Crossings Cafe', 'Okenshields', 'Big Red Barn', 'Rustys', 'Mann Cafe', 'Statler Macs', 'Morrison'] + places = ['Bear Necessities', 'North Star Marketplace', "Jansen's Market", 'StockingHallCafe', 'Marthas', 'Cafe Jennie', "Goldie's Cafe", 'Alice Cook House', 'Carl Becker House', 'Duffield', 'Green Dragon', 'Trillium', 'Olin Libe Cafe', 'Carols Cafe', 'Statler Terrace', 'Bus Stop Bagels', 'Stocking Hall', 'Kosher', 'Jansens at Bethe House', 'Keeton House', 'RPME', 'Rose House', 'Catering', 'DIN Special Event', 'Risley', "Franny's FT", 'HA3350', "McCormick's", 'Statler Banfi', "Statler Banfi's", 'Statler Regent', 'Sage', 'Straight Market', 'Concession Suite', 'Crossings Cafe', 'Okenshields', 'Big Red Barn', 'Rustys', 'Mann Cafe', 'Statler Macs', 'Morrison'] units = [] for place in places: crowd_count = randrange(6) diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index 64c4e79..c019874 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -9,6 +9,7 @@ class UpdateTransactionsController: # Converts the Vendor's name for the eatery into the name stored in our backend + @staticmethod def eatery_name(vendor_eatery_name): vendor_eatery_name = ''.join(c.lower() for c in vendor_eatery_name if c.isalpha()) if vendor_eatery_name == "bearnecessities": @@ -55,7 +56,7 @@ def eatery_name(vendor_eatery_name): return "Rose House Dining Room" elif vendor_eatery_name == "risley": return "Risley Dining Room" - elif vendor_eatery_name == "Franny's FT": + elif vendor_eatery_name == "frannysft": return "Franny's" elif vendor_eatery_name == "mccormicks": return "McCormick's at Moakley House" @@ -83,14 +84,39 @@ def __init__(self, data): self._data = data def process(self): + if "error" in self._data: + return JsonResponse({ + "success": False, + "result": None, + "error": self._data["error"] + }) + if self._data["TIMESTAMP"] == "Invalid date": + return JsonResponse({ + "success": False, + "result": None, + "error": "Invalid date" + }) recent_datetime = datetime.strptime(self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p') recent_date = recent_datetime.date() recent_time = recent_datetime.time() + num_inserted = 0 + ignored_names = set() for place in self._data["UNITS"]: - name = self.eatery_name[place["UNIT_NAME"]] - TransactionHistory.objects.create(name = name, canonical_date = recent_date, timestamp=recent_time, transaction_count=place["CROWD_COUNT"]) - # insert a billion records into the database for each one of these + name = UpdateTransactionsController.eatery_name(place["UNIT_NAME"]) + if len(name) == 0: + ignored_names.add(place["UNIT_NAME"]) + else: + num_inserted += 1 + try: + TransactionHistory.objects.create(name = name, canonical_date = recent_date, timestamp=recent_time, transaction_count=place["CROWD_COUNT"]) + except: + num_inserted -= 1 return JsonResponse({ - "hello": "hih" + "success": True, + "result": { + "num_inserted": num_inserted, + "ignored_names": list(ignored_names) + }, + "error": None }) diff --git a/transactions/migrations/0002_rename_end_timestamp_transactionhistory_timestamp_and_more.py b/transactions/migrations/0002_rename_end_timestamp_transactionhistory_timestamp_and_more.py new file mode 100644 index 0000000..30b0ac5 --- /dev/null +++ b/transactions/migrations/0002_rename_end_timestamp_transactionhistory_timestamp_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 4.0 on 2021-12-26 18:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='transactionhistory', + old_name='end_timestamp', + new_name='timestamp', + ), + migrations.AlterUniqueTogether( + name='transactionhistory', + unique_together={('name', 'timestamp', 'canonical_date')}, + ), + migrations.AddIndex( + model_name='transactionhistory', + index=models.Index(fields=['name', 'timestamp', 'canonical_date'], name='transaction_name_204126_idx'), + ), + migrations.RemoveField( + model_name='transactionhistory', + name='start_timestamp', + ), + ] diff --git a/transactions/models.py b/transactions/models.py index 755e04d..95c6c3a 100644 --- a/transactions/models.py +++ b/transactions/models.py @@ -5,7 +5,7 @@ # [transaction_count] transactions at [name] in time range [timestamp - 5 minutes, timestamp] on [canonical_date] class TransactionHistory(models.Model): class Meta: - unique_together = ('name', 'canonical_date', 'timestamp') + unique_together = ('name', 'timestamp', 'canonical_date') # idea is when fetching wait time for [name] in between [timestamp - 5, timestamp], we can look at the last 2 weeks of data for that [name, timestamp] pair indexes = [models.Index(fields = ['name', 'timestamp', 'canonical_date'])] name = models.CharField(max_length=100) diff --git a/transactions/views.py b/transactions/views.py index 4190028..4b2654d 100644 --- a/transactions/views.py +++ b/transactions/views.py @@ -12,15 +12,18 @@ def mock_vendor_endpoint(request): # Called every 5 minutes, updates TransactionHistory database def autofetch_recent_transactions(request): - # endpoint = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" - endpoint = "localhost:8000/mock_vendor_endpoint" + endpoint = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" + # endpoint = "http://localhost:8000/mock_vendor_endpoint" headers = CaseInsensitiveDict() - token = os.environ.get("CORNELL_VENDOR_TOKEN") + token = os.environ.get("CORNELL_VENDOR_TOKEN") + "sss" api_key = os.environ.get("CORNELL_VENDOR_API_KEY") headers["Accept"] = "application/json" headers["Authorization"] = "Bearer {}".format(token) headers["X-Api-Key"] = api_key resp = requests.get(endpoint, headers=headers) - return UpdateTransactionsController(resp.json()).process() + if resp.status_code == 200: + return UpdateTransactionsController(resp.json()).process() + else: + return UpdateTransactionsController({"error": resp.json()}).process() \ No newline at end of file From ed7e6b26971d7f1cdeaa9970803a30c4cbd5806a Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Sun, 26 Dec 2021 14:21:36 -0500 Subject: [PATCH 009/305] Remove empty script --- transactions/scripts/fetch_transactions.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 transactions/scripts/fetch_transactions.py diff --git a/transactions/scripts/fetch_transactions.py b/transactions/scripts/fetch_transactions.py deleted file mode 100644 index e69de29..0000000 From 765002030cf0eb539dae47fab7c095ae329766c9 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Sun, 26 Dec 2021 14:26:56 -0500 Subject: [PATCH 010/305] Remove api pycache --- api/__pycache__/__init__.cpython-38.pyc | Bin 176 -> 0 bytes api/__pycache__/__init__.cpython-39.pyc | Bin 176 -> 0 bytes api/__pycache__/admin.cpython-39.pyc | Bin 217 -> 0 bytes api/__pycache__/apps.cpython-39.pyc | Bin 447 -> 0 bytes api/__pycache__/models.cpython-39.pyc | Bin 586 -> 0 bytes api/__pycache__/urls.cpython-38.pyc | Bin 351 -> 0 bytes api/__pycache__/urls.cpython-39.pyc | Bin 351 -> 0 bytes api/__pycache__/views.cpython-38.pyc | Bin 1433 -> 0 bytes api/__pycache__/views.cpython-39.pyc | Bin 1433 -> 0 bytes 9 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 api/__pycache__/__init__.cpython-38.pyc delete mode 100644 api/__pycache__/__init__.cpython-39.pyc delete mode 100644 api/__pycache__/admin.cpython-39.pyc delete mode 100644 api/__pycache__/apps.cpython-39.pyc delete mode 100644 api/__pycache__/models.cpython-39.pyc delete mode 100644 api/__pycache__/urls.cpython-38.pyc delete mode 100644 api/__pycache__/urls.cpython-39.pyc delete mode 100644 api/__pycache__/views.cpython-38.pyc delete mode 100644 api/__pycache__/views.cpython-39.pyc diff --git a/api/__pycache__/__init__.cpython-38.pyc b/api/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 263f79a7e1069d71e1370b79baf37ad9003cf7fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmWIL<>g`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10WKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 oXQ$?+=oc3y>&M4u=4F<|$LkeT-r}&y%}*)KNwou6_Zf&80KjW4!2kdN diff --git a/api/__pycache__/__init__.cpython-39.pyc b/api/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 1b1bd62ad5b18dcf4b84f59698195f06abafe001..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176 zcmYe~<>g`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10WKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 oXQ$?+=oc3y>&M4u=4F<|$LkeT-r}&y%}*)KNwou6_Zf&80LZH?%>V!Z diff --git a/api/__pycache__/admin.cpython-39.pyc b/api/__pycache__/admin.cpython-39.pyc deleted file mode 100644 index 5dfeb2319c3e3259ccfa5ffa835f00d1a902d352..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 217 zcmYjKI|{-;6x>Y^M2L8e6v5;oQ%Yp8BXRh`D~V}0;!>Ap69@TJp13*K zR9I8HvT%LDowyKfkk6LwJvh(s!C2>@G)Jo>%dBWLnA)>-Rb_Bt2CfP48eGeMmK3Ui cq~PKVMl$aN8%EQr?Z$I$Wust0apsLAU-c_F7XSbN diff --git a/api/__pycache__/apps.cpython-39.pyc b/api/__pycache__/apps.cpython-39.pyc deleted file mode 100644 index 922acece0b46d2f08d0f0ea77615cbc9df053a01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 447 zcmYjNJ5Iwu5Z(1UQpl%4v`DlrVqXBE1S05=5TbFj+IS|xu--LZ8x&d)1vlXi9Dp;p zrQ!-y%*K((Nb`ShG&5>E9x#&6w=eZh{@W+J5>hfHbw^}?0oSbHDd+5hK>*^Ffk6Hu z(g4D17WIAz#UkGB&C2r3nq|Jqd}UWSB~wy&Kn7XLff1Xh0)jLIK~q9c&pZ0_A>1jm zvN0^;!UAd+pXRGsQ`_?#HMByN`DN5=`%)TJpp@-EN&+_8$0I2p8l|_LeZZw^bS)Lp z<#NLheeZ)+{tFxPdzIWctXz^=V{BDnZf>oHWNx#jKvTPfPMhOnV(@8#s>W)abZ)ZH z4Nev+yGH{F?L}I;xLj9)hWkf4Z~8pso62P*+Tr#(kE{Z}gU9}F(?vt7wf*DntP}r0 I=dmO42Rl1)umAu6 diff --git a/api/__pycache__/models.cpython-39.pyc b/api/__pycache__/models.cpython-39.pyc deleted file mode 100644 index 803641a1b0e42969c9ac545a8a8ab7749bf0895b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmYjPy>in)5WbV-AQ?Fscmz5ZjtUwIh6#g*pMo?A%{4b??T#&Tx)XONf@>H$-T_M9 zglD*xDz8ArO3IA0n*I59rQMxxr51}hBk|w=sHdEjAVt1UzNR9D?20XCxAY!c@_5o(K<(=R-j|fX1*v~V2ASZ%M1tu)0}ja#E@qhi zJo&}M1M)ff;?B>>KO|g2N@KG4_}VGsRUH~@-Znm1cen*=Nu%ej`XV)&ZP;Bd7crO8 zs1~J+b17-7UJr38<)>chsp1m)G8V6P%Drw-L(GPJEY>REm=LG6GS)P;(h?{ci|eMn zE1mhET#$hh6t$gLpn)Td-~ewe3y#&D<%CAI-n3>Vrq;tJ)f4ox{fL ztcGfB>%K)3eD$*H*7&(Hc(X!U#2u>1tv0&H)kf7HX?mr)rkW0L-W}Y)`Ha*Lx6XOV t^YIz;m&9cOdu6uv95xdXv-iX8#`1BsDLSJLF@HYM_=5fz<_GS|>_59`p7{U( diff --git a/api/__pycache__/urls.cpython-38.pyc b/api/__pycache__/urls.cpython-38.pyc deleted file mode 100644 index d6d136373316436b22761a5594abebeaa671f47c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 zcmYk1J5Iwu5I}eROCX<;JGe;p0ucf!H4;K;+(eFNl)Qdi@4B(6ZHhGO%P|`y> z=tr>2O=;(y)8qm!aj{3_;aAIUN^47vYuX~Vw8p$eS0VKyuHZRrhCYPzN1pNXs8)oI zRAG&=&S7P?R)bpFqN~xgr0$z$iF;-6FgtfUs!N(}bceG|UOb}#MXpeOsd;nkhl|=m Sr}5U`L_@|n7>83i{rv-c=U&JF diff --git a/api/__pycache__/urls.cpython-39.pyc b/api/__pycache__/urls.cpython-39.pyc deleted file mode 100644 index 1f8b2896809abf242e7ecc55c5609b576fadf444..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 zcmYk1J5B>J5J2tyB!PTN?vNt!1tJ7eY9xecwuzjb(Po3!UU_yCasW!Mfkek4IK{S9 zxdKJRIB6KkZ}eo%Pmjmfg5&M$TYqr=3GzRBEO-29!2ki$h)QNs3Wy*!Q59#g6euCV zc$+{9z4w@{&z3?zSP^AC7(9vT)sZ`hsqB(kH=CopNV+R=Po3z}(gMDQZ>uZJqJkc# z{cZ?bZ3{PVy&)fQiBAKPfUsJ2Q#e-`T+s%xp%v-|eTmc!xq|1gS^5+{9C^m?qk1Ov zq;hAi^BzmPaR$`V(@S z8S%-@(eGd%4dl>==u_;qr#wM{0Bt(tT8L7lOYl!}NDXIyn%P0KSwrx=|NW0>-9zY4 zvzUK;SUiKSK7dd}F-Iv5aY{mh&6;p0bYOJ2n|h&VZ7%oIN?5UTj|XWrtlGKHYiT{K z+j)gI(q`DS^MJR~g>b>ntGu0d!j7HS_;=}IxQG#}vjx^<6;?e%taFCO$GuB z!X?_It=~|1gD%iEjLVelqHgCSM2xzmaA~AjQPoMxDC0WncX849S&3|isa#}=!P?&t zSsZDYwKs&!80VWwmSp3;I0D<^W)kb!>DMyKb0)#u*#gIM`cjHX{$jc}&5frxo|JhwR)6L=Nzvf>Yh8NMEmam8-VfppK62M-d3bcU>}XU zD{LrD)s<8D`XtFlqVSZCq%Itm(Qc)1^Qk^50>kh`WUOcb(`dxS(Qphn4kQ65g{K8L zktRmzf^?mtBH6DKrnKys1HNQx!u1uB-^23LPH&)?RJ~YanUInt*@57+w<+RD$}+8b z&+~kf9riMIw8|{%UOBAp^9ftskK(rwT~EolcbKpvwU$o{FUcr7z76hG3k2aBCeVD` z{@eHD9kBdA&u6f!kp_|!Q*v%o>=c(&sdI_7ci{uwQY+M@o~7$sy1_NwO8?~U`gS^E zdxPhXw$}&yt5I|~-hTb?{;SdBtzTX|dh%eje(%YH{;S=M9r`eaf4!x0$s(#uIt&vk z8xV(V8ZoaR+aTSlH2;&V8mSvuGIGO+VJ}yV%#oKj!2p#x8p~T|%K$IMuql?uHUo$D zPBpYxAvb@7J!Os}OAW|7%`e8U>rI+ZPI!B^{f*MID8gH_6Sjcnt562U!_wFLWkFhi zU+(^IZA$Yo4DZZ-`K>Z7=`X`Mn@twS99_%VA`YIWf=)R5$<(j93(_V5Zjc6^Ys4g{ RX)bpEF{nvE{_>ka?O&Q8Z&&~T diff --git a/api/__pycache__/views.cpython-39.pyc b/api/__pycache__/views.cpython-39.pyc deleted file mode 100644 index 59834d44fd48ecf7618df377be537387cd5519e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1433 zcmZ{k%WfMt6oyI8MWd^w+Od-+-E|`%mf$oO1PF>Gwh_1sBk-jGxDm!8B~Qv3PD#qP zjCkd)3iuuDrGY+3(MOnVS9yX00owGCV7T_AHV({_X33e zw2S#CgvE2{`Zo}U7#1kS6sLp|Y}Z71)PvCzej3og*?bYE6cHF!szDgJAB1XK<7kHCbc=a6d&N&*Bb8H){v&IFX zORULSzaV;xEwDC>%Z%)zZs$EjjJl-oS!{SwHA%`D7bf2B;-c|`me~&1xy&?&b+|6G zVQgU5UY9E4LTn^il8v|JG1wM2lA)QMexu?%=L*c7O>nHHuaunRFQLkr;7}F|XHU2W+tMp3$Aa%3a&Gj@zSqTcQSpI_6U_(wWXN-qKzKuJ|HsYa!B~O5 z4qZP0G3XLAe1*@jA@8v9F8wQXqX8q#yTB`m`4`?9G2uJF24I5i54HW>{${LgVDGKF zYiub^^|e=o<}}GhvIw+^l_@-)v2LaC^Qk#4BFpepX1r(t(|9E0@n8%%4iy0>MPMX2 zQ8q^9gLJ*3qWI4fu8r#01EJ!2BFr^XU&HdlPJdr>rTasfWl|}gWQS6){)QY*Ql1&z zf05@K{HUMt<5liZ_se1RKuq}RK|K5kqU*Wh~j&5{Ax3YbDe{Cxr z@xA>QkGIzL4_4#&XuS32(RZ&$Pd0yi`S|I>(b|Kj54T_MuJ5o%LwM^gohu$QZPQ_p zFx7xKRMU$63#tv$tt$JTRMkq|%952^RxEq9Vr7oJx(x=X%+Xlgv0D~+DV9yKJa!p4 zaCho~!wR|iE$nG~6gg@@?xX(^dPHzkG7Qx>u-jcJB3o59AIkgP)i$Q0&fdn^SoRK$ z$z2-I7QCSzMA9pZOu=^s(c7PlpVynRpB#F3w*7_Db13MY*$G!b^Hr!K>tX5h{jwk( zz)yGow>Fji7}9&QUw)}fOZv-j&SjIsF-O;Nc8H^Asbmwuzqj?P?}M~Sgd3!R=Nhre SX`7Gze=KSek-x%bRQ(qbQg0Ff From 588d0e3b2bc28205655d95e121d23cc5198882f2 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Sun, 26 Dec 2021 14:29:46 -0500 Subject: [PATCH 011/305] Remove more pycache --- api/datatype/__pycache__/Cafe.cpython-39.pyc | Bin 1695 -> 0 bytes .../__pycache__/CafeEvent.cpython-39.pyc | Bin 600 -> 0 bytes api/datatype/__pycache__/CafeMenu.cpython-39.pyc | Bin 830 -> 0 bytes .../__pycache__/DiningHall.cpython-39.pyc | Bin 1609 -> 0 bytes .../__pycache__/DiningHallEvent.cpython-39.pyc | Bin 1141 -> 0 bytes .../__pycache__/DiningHallMenu.cpython-39.pyc | Bin 897 -> 0 bytes .../DiningHallMenuCategory.cpython-39.pyc | Bin 947 -> 0 bytes api/datatype/__pycache__/Eatery.cpython-39.pyc | Bin 807 -> 0 bytes api/datatype/__pycache__/Event.cpython-39.pyc | Bin 1986 -> 0 bytes api/datatype/__pycache__/MenuItem.cpython-39.pyc | Bin 885 -> 0 bytes api/datatype/__pycache__/__init__.cpython-39.pyc | Bin 185 -> 0 bytes api/dfg/__pycache__/Concat.cpython-38.pyc | Bin 949 -> 0 bytes api/dfg/__pycache__/Concat.cpython-39.pyc | Bin 953 -> 0 bytes .../__pycache__/CornellDiningNow.cpython-39.pyc | Bin 5116 -> 0 bytes api/dfg/__pycache__/DfgNode.cpython-38.pyc | Bin 723 -> 0 bytes api/dfg/__pycache__/DfgNode.cpython-39.pyc | Bin 723 -> 0 bytes .../DictResponseWrapper.cpython-39.pyc | Bin 1199 -> 0 bytes .../__pycache__/EateryGroupByType.cpython-39.pyc | Bin 1367 -> 0 bytes api/dfg/__pycache__/EateryToJson.cpython-39.pyc | Bin 1715 -> 0 bytes .../__pycache__/ExternalEateries.cpython-39.pyc | Bin 4351 -> 0 bytes .../GoogleSheetsEateries.cpython-39.pyc | Bin 5405 -> 0 bytes api/dfg/__pycache__/__init__.cpython-38.pyc | Bin 180 -> 0 bytes api/dfg/__pycache__/__init__.cpython-39.pyc | Bin 180 -> 0 bytes .../__pycache__/0001_initial.cpython-39.pyc | Bin 844 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 187 -> 0 bytes .../__pycache__/0001_initial.cpython-39.pyc | Bin 853 -> 0 bytes .../__pycache__/__init__.cpython-39.pyc | Bin 196 -> 0 bytes 27 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 api/datatype/__pycache__/Cafe.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/CafeEvent.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/CafeMenu.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/DiningHall.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/DiningHallEvent.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/DiningHallMenu.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/DiningHallMenuCategory.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/Eatery.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/Event.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/MenuItem.cpython-39.pyc delete mode 100644 api/datatype/__pycache__/__init__.cpython-39.pyc delete mode 100644 api/dfg/__pycache__/Concat.cpython-38.pyc delete mode 100644 api/dfg/__pycache__/Concat.cpython-39.pyc delete mode 100644 api/dfg/__pycache__/CornellDiningNow.cpython-39.pyc delete mode 100644 api/dfg/__pycache__/DfgNode.cpython-38.pyc delete mode 100644 api/dfg/__pycache__/DfgNode.cpython-39.pyc delete mode 100644 api/dfg/__pycache__/DictResponseWrapper.cpython-39.pyc delete mode 100644 api/dfg/__pycache__/EateryGroupByType.cpython-39.pyc delete mode 100644 api/dfg/__pycache__/EateryToJson.cpython-39.pyc delete mode 100644 api/dfg/__pycache__/ExternalEateries.cpython-39.pyc delete mode 100644 api/dfg/__pycache__/GoogleSheetsEateries.cpython-39.pyc delete mode 100644 api/dfg/__pycache__/__init__.cpython-38.pyc delete mode 100644 api/dfg/__pycache__/__init__.cpython-39.pyc delete mode 100644 api/migrations/__pycache__/0001_initial.cpython-39.pyc delete mode 100644 api/migrations/__pycache__/__init__.cpython-39.pyc delete mode 100644 transactions/migrations/__pycache__/0001_initial.cpython-39.pyc delete mode 100644 transactions/migrations/__pycache__/__init__.cpython-39.pyc diff --git a/api/datatype/__pycache__/Cafe.cpython-39.pyc b/api/datatype/__pycache__/Cafe.cpython-39.pyc deleted file mode 100644 index 3bae7646681646711212dba2dacdaafd6c3e2807..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1695 zcmaJ>y>A;g6eoGS(@8oxuG6?p3lyCSXt0!_qhYj&jZRgEpvwWp;c}$hQ+OXCsRR<# zNd_`?={r_P-kWaxX8)Y?f~68zocJ$}58-(x-)^a!+HfB89IhlKow zliQ8M)^00<(eA{9+3MVTp91!>^rK=G8rKd!H0-p zwR4l@TIMd2=z(#u%57=qLb|SM>hq1rEA(jR0Xl+mupQ>PGnXy&cdF3z&DE^U@+j53L!0 z*|w+h#kiI)kEGA{c=I`Ws^;?OXgBe2DN6;lbZ z2fw_@P(~fwWHZy$L+04%kqf<%Z~5bT$Eq~8XsY&P1n2z$gDgMwPHnf3j#X3S%AA0( zTRM#Xl`hD{Ewlb#uRq3>A0fOIUsQYs!ypYvhtosKeUD!+liOwugUJMFfUu9G$7;+n z*UPf15p#udl4Z~5x!P(1W3}Vxf{x&57fw}^Ta7vLywg~Uj<*ZDbg?Zfxom2wG2%Yy z-efn}Rze|_f)e}3`1`odGb9EH>1!~aF=vzkhY{JI`v-(Vf5TXeynd7P?#?yt9qzdp zA5S#FUk^-Qv}Jwn4b&JMcOR-}w5y)c=2g{540mu>gYS{2xI4U~z(}5XrH{pfca-@2 fZeoKr-E!2sjE=hacoXRpWT?+qLK7A7LUV!&NVrR_C z#48{nahF2dlkST@<^0`uKGke)0>=Btn`$wDPa>-kBRRqJhX@)p%OGb%#^4-ur1=aq zH*Cs>k&Z8*-T1;PXtSzu_F!yCiLFM2GsfH_upT) b-h-o(x?n|wp2U$-l^o4`AJd9zxo!Rn9cPc< diff --git a/api/datatype/__pycache__/CafeMenu.cpython-39.pyc b/api/datatype/__pycache__/CafeMenu.cpython-39.pyc deleted file mode 100644 index f2214f33780addcb00cb903ad4e944be72cbe3a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 830 zcmZuvy>1gh5T3ms`>-4-pit0IAtV>R;{hxS6iX-tZX%kKV70m3z=iwE?p=^0Q^hYp zPXT4#gMyj{XnE1ai1}4k~FI3SS)hbWuV0Ufd9^AGU_R+ZpJ;d$L>*%FX+C)iN6s6WGE#8)yj230mLM!C`>7JO(yOT3 zrYBjWrY9nKrwSQ~DvhM5MZKz2)K?l-D;sd07HQ4-0ADk0#q9F#Hln{x7+&{_>@Aml zbR;%q18X>g1za&?ScS^sQ}aMXqKg>PRhKRQ!js`z~!TD)i(&?EGrX{nK>GLc-LvBiS8{_O;C4G z`Zny}23*pdIWI)6IJYCt^HMe$*<;Sn8ip|Wsk5iD zYGEp#6be>TomJ;D$76O|upSr42YdjG2tiY{pMpA$}!Juob`(U$VSx4g#(rHKi%RllaT zo2I$Sj7igL^6H1tw?=9+%Im7CwU$NoW39yKw9aQG+By27X-?(SQ6--pNf+1A>UZ== z&E(OeEPpI3G0K`^1i6s4^G1%gE*v&aEU+t@Z9FdMXsqMFoT zcbnB;8AwL!FzN(@HDz#?*m+OjavE+!d--S5f1-_ zcThrd??X2p00|bicG#_4`xc1~C^{1NK>!L+ZNj?*=>-HC$-HEzS#S8j`i|Ti z*4XG+6{gMWvN`z-&ie~GS$wi%()y6$v8wY-nG+B;8?OET%9~X}eF*31_YmH2-#wW7 z2)ei`#GCI$+~5L>gfq^BRjRO>LB>Wi4h3O!iKnC-7KK9@>C0d2Y8LOmCM3?|l;!C;2m zqaI`_;H!fJLg9Puc_HTQ>o~eO*0>WMju4+hB;YQGES@*;^f`L^V^B#Bp+<(A8X2zM zNR45)AKX~k`s)l|8fS5G`-aMD!DTq!zGILx4Gps4@yeMem}xF7F^#>@1Eewg2XjDf A`2YX_ diff --git a/api/datatype/__pycache__/DiningHallEvent.cpython-39.pyc b/api/datatype/__pycache__/DiningHallEvent.cpython-39.pyc deleted file mode 100644 index 9dacd853a3a8e91b43dc2048c575025316711db2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1141 zcmZ8gy>1jS5Vm){KbPDkQTT_BiYt<(qCyD7A!rB%qS?mUtPSkp^)B{aP@?M+DHU&! z0?IrBJx_o{|!ujA<%yQ_+ESq3HgPC#l~Rp7|f2q z2_mQ>HO(nS9H^iU^U&i^u{z2lkC}?=Bu^-LPDCW)BO+oM9)@`;lGkLI{)U~%kh=6$ z+f|J&6yWIDp41(HwNa%j{q9Af)Jv%+@P~<_lO?WMZ1MTPJ^&y|P6f#W8A>K2=&?+0 z0oRl0n7pMT5aCfDPG=%IB6;taFp{qTli00aoaPCfMs8I|TbjyKb8BUx8(o!!;>g0S z+paJj@2Xk?s#`aZS}Y7%4V=q4H>8L-0t0P{y$h}*hh$8r!8DvQK|h0fQ#vI)m`313 zL4a4VGYm}+Vhz^Dtb!Xd7uiWGjY~NPg*whnij#euj2*L5y`BBJywC@nQrF|WRE4#i zpOQ0~y|L2RtZcMyjFFXo-zbrdnsNdObT)g^wj;ThY58eOLf&RSn|-!+)kJRX7Uc)X zJ1g2M16hi0zm?gNlH2XRSp#mEfki;lmd$e4TEz#!=8(w zOz^V&5pEM|w>_`jw!ixZuc*6oY2qR`e2ku$OYtS(3-Gl4_6wXmJLKfjhqII)VG%8| K>j!-bJ^TmJ%M54$ diff --git a/api/datatype/__pycache__/DiningHallMenu.cpython-39.pyc b/api/datatype/__pycache__/DiningHallMenu.cpython-39.pyc deleted file mode 100644 index 121b158395b62b72e7408fe0907953f45b9d62bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 897 zcmZuvJ&)5s5S_K15274G2!Uv5PC^P&Y<@r#fi4m>q&d-8LaROR5`1jdzFixXQ>ZR~ z;QA||y#GN-Md_A`nvRMYJBgBrm1aL@-`n?Q#nDj@u>JV?T^uujU$oe5J}zEhxF^_2 zkW9mz#f-r#ke>8cApI*Z_GEAhgVt}%fdQ)?PEwPa*=wQo8)eE9QK(sF7YQBjwk`Nm z+*J@W39+Yq6+A|w8&(qpaZ3iWwSu@U+ep%>?(U=CvZ|Lf45@O7w?ja4iW3Y+I+tt- z9AzL5o7w<(23{37rQh2YE^fhgaQG{uw@z6XC7CgqwJJ3qGA*M?mXvd43KzZ1^NIQt z8TEOnklHSyW*DC7QVq{U@=+NXi9C&@C`7TyRkZ);Fke(%&e5iV^EPt2`rYjE(LvrO zC&A)u8_l=6?%_hBS1VY;IZWV+p*nxMcY%^g?J0(PfZYs}-}S$+CHo5B;M%*u`qkR8 zdYvNU=Pom7jTb(zx@&V6^?B2E5u)mibn1#Eo9AQl_74VJK0A2-8Y>*>ED_p`ak9D8 z_|GiKKftyGcn4!+%fXKd*RWIk4rrqhbH#bp<9wdUQq#Q8`9&%8`XnGyODJel zspyt8S^AM=l%uZe-S_LA>A`pZ%p;8mG%99Hge9% diff --git a/api/datatype/__pycache__/DiningHallMenuCategory.cpython-39.pyc b/api/datatype/__pycache__/DiningHallMenuCategory.cpython-39.pyc deleted file mode 100644 index d267d3c46f6dcdc53944c8d33c2913d2e1434941..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 947 zcmaJ=y>1gh5T4yT+lR3cML^Jm78mv*YRdw}5>VhKqB$vAU2Ye1VecoqXOJyZ#VD$*Y@eu>~PKRCc@bCoL?xT<( znTCu-jKMRITzV^z-WiX$^beue_PJoK%fw@SY@4Lq18iVH9Setzljl~u-uNs;G;F)Gd96k3Mk zBAI0>uWb0VEXV3yn5*}Fg{hc%xc>By^-T4TOSUkR_mJQxusEZ`lc7)2-xgODaPRz6r==wm3ML5RmWf&*Fj-@(Z1Z!ofKPbuR!EQM4uv z%_&8j)vVz;?`>X-T)ZPnsAxk}bi?vU@dZia-|&v4Gze9A4QKMc^Hpn#dR}7Wp+w*u zgSwXhn&ecGoN2D;EmchM4OCExjxJ+8(#bP2po(s}iq&WnoP|B{hP}CHS zE*YfT3)-k}K^?Y!O|OZBZWd%q=N2iXEC{FTa~p%RC3XzQpO4uGr>)D%)|l2>U71g9 zt+JE0TsGQxm%Zt_6a6(a`f8^8^4W0A&g!L}ofYM0ZB*uLnZY0mzv^^0(0tylLLy~l zDlaA0ay?(MKHA0iv3bfv?AMh!Z$spKVSV6`vlQ@VFKELW@7wd_hWvm*E{Gk$JuF~+ z`mNOc4pLzgxU*A)1B62a%$$jT_|Pqc{;%^aNbgeiIh#N zmNnW_DZea>dKcl&Tl)y}if-lCA@NnC*R9bOf11VC?PG*{7QKa3EOv)oxF-OKV1K7$ z==P2=L1P-<_3<t-8uw3shp diff --git a/api/datatype/__pycache__/Event.cpython-39.pyc b/api/datatype/__pycache__/Event.cpython-39.pyc deleted file mode 100644 index 2767634f0b2139242d8fd3252573a460dee8f3a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1986 zcmZWqOHU(55bo}2kH^Na8$g7xc{uM(1UN!DMSwO6t0))efcD}j8V%D88JLIZ9+O}> ziWDQ^9CKwi5*+;xa?ii$YfkwKQKU^(kBwost*)+~s_y!#x~d$`&({fzU-G}g4_re2 z#?9SPfz4x>`V5F5f+i%T9ZHdB2}_-hld=v=d57C?PEzSqz9xc;$^{XXA?vu(kz7`e ztI`whb1tgFyWpLgtV{O|A}LDj>P)GqfmU72f!3UG_KDy42W%6c8t>a~5$9Q$_>SQs zETr*}7I7*KN7{~1^X}lVc??s34n&d;6{N!~A};82M=;@lol1!puyVjiCm5@u2G;AQ z^5jfr#n>?mQJCdf9EC|>tv}F3sEPnB1DAHqyv)RG8=>*(K%T+;3Z`BGGN1zzkO2cq z2M$ms$UYgUnu}h7 zJlt~H3zJf!5UFwEtWXco;%i8jkl@4KAs7*b!N&~H7%q=93cvpvb$Pwi5_iI1X5gpJoqSrOpHG#b~VhFn|M724Ba#TEnKZ?`)ZYXqFy$@u})w{4j zBgRImttaXul-laBHSsZQ*I`c3fBK^T=$o{w@~%|PP#u*GaI;yK!%p8@Z`w%g^xgF) zT)P7)i%DmzMm3j_=b5x6W}z64aklrAFlMd5V)3@iUb2e~R%3H*uw~{s I3rj2fe=9(&zyJUM diff --git a/api/datatype/__pycache__/MenuItem.cpython-39.pyc b/api/datatype/__pycache__/MenuItem.cpython-39.pyc deleted file mode 100644 index 9d9d867444f322206d09a8924fce6e62d4857464..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 885 zcmZ`%y^hmB5Z+yHY$qfrAPTOeydt=xLg)}DAqvtU8W*e_?;i1C?}ptqC{ZMeOUoPF z3*6!?#W*}h~FcLcLqK>1$ zovP2eao7YryZ(0quOg&E06H%OS`Md1dNEzyIiT9V|Fstc(E+#(%9@-bQ#b(J2-}t4 zVW4&h9)A{2yy+%_N9V9Z!Z_tutMQ@owZjxoMF?D_5MdxhV`N*SJ{018Th!MtLkEkf z%0~IQk%7yq^!65{4SZ%y9U|wg9f55T?+r9~5Ei@d&IC`zA>I@gG)@_1KZj}eGPSt3 Re+7p0@eb1yjQ$QxegOd-#ghO4 diff --git a/api/datatype/__pycache__/__init__.cpython-39.pyc b/api/datatype/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index ccc7ec836bccd2e8820e38ad49b443a51e81b89d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 185 zcmYe~<>g`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10QKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 xXQ$?+=oc3y>!&1^B$iYbr0U1VXXa&=#K-FuRNmsS$<0qG%}KQb+4~uY832>CF<}4z diff --git a/api/dfg/__pycache__/Concat.cpython-38.pyc b/api/dfg/__pycache__/Concat.cpython-38.pyc deleted file mode 100644 index 437e26797a2a0a1ab355fedf5b246cd78b60a632..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 949 zcmZ8f!EO^V5FM|*$tG>p5^>>%xaCmx1{Z{i8i`vtL={pbWNmFHUAoy#?G07YRvhS= z4?z4tzJtHmD<^(|6Eof#}BR(D6U`MA(VVYnw8qcc?Z0v7{#52k_PMm)jV zKbWs6rlA%(e1K_3cXStRS4X;sHqw3Ua!_~9i(Dn;n$?lIN;7Rut^zE$74k{XQ5_9f zu?i&OpTL@pEe$mcYT-;avmpj+uo0R+@8b(+tcz8V=Y_Q<&990~$D=~67A7xU{ARfv znNM+UZcj~88haNv=JYaKnbXTeU7K9T&Z=0?=5d4a`SPxgq)hX)l(K_uoQKLjA8+#8 zoFekJLa<2Rp}J?d%;BC_@DWDvVQ?=hUI`p3_{Lu1{!PE)6R=dg87{P4^lOpWxvM+Z zw~e*Nt+KKX8_sBAKep}BS%j#PER&KVM@e8J=(Cfc1++P_2b4gQdaelpfoy|WGY_U| z<(pCm&-){E{%2$Nplvqxn$YuUh94mqTl!B;<&&Ma>ro$Q eqKNz5@i1sg{Tsty+ug0BR}GHW#DJ&jkpBfTzr3aZ diff --git a/api/dfg/__pycache__/Concat.cpython-39.pyc b/api/dfg/__pycache__/Concat.cpython-39.pyc deleted file mode 100644 index dc1f481fafa8fd5f16ba6480405afe83d1d4bfd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 953 zcmZ8f!EO^V5FM|*$tG=8s466GoRD%TdxHx?MUBKQ93q4i30Yg)NtbSRQ+v~@Xe$o% z%m*NjT=E_K#a=n_3!IqoCSk)!KwVa#0bn)xO zVrZ`8+}xa)q%`(6Zp_JfwlpW_iMlkoj-6Gpp3dS1<+H_Y9Z8wyX(?p~+c*!EeL7s{ zH#tS*ZG>Ra;kT&nDK0a(;}v{_A$)jrCn{cviltBmKiF6H0&`dWijTq4oo2w$cG0gz zVrQ=IT;4R+8n?{KI&65Osr~4-M`sbHO0rB!iX0_@iJ;GpgBIB4#12>jPU`t41O&7V zX2m?1rj>6>9X$6B(fOZ|?W49C*=a&gs2P5SU}Wh}HI*N1y={+rPaCIfx+v2kKW_Cp zTC?9uj|M%WAW!B-%DOM*ywJ;x{Cz2}mPxkGh%9xbrB}4{x$Ge=^k9~TX$`qW;V}|s jPihn#@hFP8-yIEtrqsVN{I%`fI(pgQcts3&s`mL`3;n&# diff --git a/api/dfg/__pycache__/CornellDiningNow.cpython-39.pyc b/api/dfg/__pycache__/CornellDiningNow.cpython-39.pyc deleted file mode 100644 index f4e4d7ad3476ed314a7cfb220558db319698641b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5116 zcmb7I&2JmW72nxiE|(NVeOQtu#ck8ZNy8*2755`);y89CH&J6VMI1Ni0`6kX8A-I4 zTzYnCTLf94u?rLdbZ8Gf~I$V%ZI+TV=1zav_{)&3dye7sI*Myf@GCRCubj z;4QFR3Kv^T-jXmrFyyo>KQd%lSr0Amw6v}pm6?CQN~0pu+>LfKYD36xY;D&gsp^o6 z8_jmJz5QVjhIq5rgDr)pOV4Yn-Gy8r`N}=jPN;#Dn~7>co*4~2YF-+tsN;H&sO?Da zqH}q?1sYqTwL!KW|K;#<2_^muDrI;=8lEYIXDM6dl%w)n=8CZbKYuBtCGAJH=cB|HytQyeV#TSm(+D>q^ks_?yNoMf9DLCDxZ&KO?81E6W+y&GPs>kDrxu zteZO=KYuuWUY=t8Db_E@1&m*mORQUzr{(e^!&{Ooh{S3-yB_Jb3d779_2_=@-JK-q z#P8HDX8Z~e9@xLJ+r{j; zW~>=~OA2!M9aBGmY(chHj6rVO7&!ghhH-OYkncPDPGauo`}qTLfJC_a>%Max^P_)m z#ds(_M;PG%r=B`-5+vO?&BxtFL&Y(}AV}S#4@>hcXoGE4aZ^{TyImD0acXU=L{H;W z()sm|KdE22dez_9tZ&vo@Nax_HMPkZY2nI)hUz4Mc-!BPrSF4*jCcu^AxyDgmc^=B5+z{^SInEX)mxbueKfLS>K8Fgmr;!&#Ds!KY#k*& zk4g$@O6!r)=h$u_V)u-J=nH+8kkdB~1fpr|o0-J1%+)pJ>Qm>vPV5Bvstd?8=Inn$V@kQUkV@lK?Zv>d_LK|<^wb#{z{tkWlWP%XJ3Md?jUqZ+tAx-S9)}A>qKz{H)$nWie**7U3kY5{EeL+NlZojJ_xF?GfN2aEy;gGA6#z&j^$M+#A1*Xa`PYo2tw^_m z1d#d>HL)RYlOe^7nhAg>NJA4^E~CVhnS>Z%=80q5rVH+LxH$hSIlY&^mREqBxy0Y3~sva`iO7qk=Hm*-dR) z2h{U3%*m-o+{tT$z1UX-678UZf-L^Aq^)NedvH0>=uN=%#naf-2tV*cHGWx_4Tml$poV~(K> zl1?p*hY-RuX{0_dncA634g`pV=>w#4eqaMRR$}+c`p2*=bH`gliM>PHul@!E0{XR8ZqKXna>yp<$C7I_T?`GLRl%2(Y^>}c`p$iC&(@3_!28}&| ziB0J4pu{ht8Yhu54q=osv=xC){#$Z;=f06^Kq&>L6tZrTN(josocaY`o(Y{d1^|uB z;qZog5A8z_2|m0JK|@fnkYgFqEGKA@1tyx}*MvvKtb2Ll=S}&59Rw1ZA6kynPV z={thW?@%(>y!Tw+j0fl$pc%_7+9fHwn8fICybU_JSsn;Jui_5!2R>_N_L`b#(p%;v zZDf}#{}3SbAz;{-sY4bI;@1-&lNK+8%{V)Qeu18&GwAcv;o0AHG#m4E>K-`}me8$@ zwQw^|ZtPs#MX>oX;+gg7kf^6nL3>)#+`W3dsKm{U(XA9FWz34a%T_bf_GZ!UJ*)p=8_ zPMKo9Y>K5hJV{1bTEugK{Zv^_9bATRIA^oH8T=!lio0U<@P9YjqSfe*<(=lH0N;Uo zO?6}V{~X>9y}7a0b-KGy;&_V_`fEf)be8azCfcs>8H)(%#6TLfNg2Z_c4lng$dqh2 zIn8_N8C__i^&>7JfO{`Irzg8Tb?JCX$I5IY7~<<#6v*W`4QJrENU-EPL<#gwC&3ec G*8c%KA>Z== diff --git a/api/dfg/__pycache__/DfgNode.cpython-38.pyc b/api/dfg/__pycache__/DfgNode.cpython-38.pyc deleted file mode 100644 index 63f6ad66c6d89df8c95aac557ff674f667d37c43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 723 zcmZ`$%}&EG3{Ki^6xbvLufS!8krzN{h#_v{fRNBGQHir3txHF;F~$Xn?PYf74S0)O zc?52puv?0l085`;$8kP??sdJs!_c0x*W?A`8znz2#H59A`{*thFyJf1GUzf0u2>^f zc)no)W-Wv}M5kGo1M3FBAy~0)2q8omBd9olu!EnYDUo z23ZQCovGPao2e70lSy0O3!~>volLb|h$1v^)0uAGCTgS&2xpamUSE98dNf)1SW1l{ri$4wqO$lu z(dPOJI-1$;{z0}2+9^BCH%sg#-$5ztN4?Xi(iTiI)>8T%DaRSiQu?k*c{fYaqGD+P sTO(KBWTZcoYqaHN9BGV$IE=&mtkd9mEY^|V|8DZ}aY1-O-R}tY33zXnkF$y%Yp z^EC@FJ4U#Dbebg`SP}q-V8N0QLWnR%P=h+g8f@SjvES(Q2i+XB;@DFeQB!__a5UwV z&Dje+cgdE#yX3xc_Mr4+I?7Ee^pVq9-$$t(IA6cHr?_M`#c&<2*grui^PHod%#L#cN>~bf}N$b+H#;h z^9wj~+5f>WK;q&nC;kFbC1$+cvK6f*&)Ck`^L^iplFZM~5Ewsyd{3@&LVn?9dU&vT z1k>CBBZ;Ir>C-Ny!Hh+EI7 zd`tVV(E*qd(M@oFWcO)+p-4&J8O7F#E zx3ZZJ)XHX(?x;e>NtMNNs~4X`Xtmn2p%7V-)k0uugNO(X=>F1q;gismV%W*RD_G&@ zFwI*q`?n=3c)|nL<@a&4>Jujw1u| z272Luf8w1Jzed(z1-0?oFF~`08KkK)#(FZT6YDFjOT8X>PDa~&I;o0N)N)g6_!a^> z>m|BpY_PNIJXKEYu|XOrNSfri5V$k9z!1t9qn9W<4ueY|j>j2gG&sIUr1AALBB5Y7 zT{|EQ6sSi#L^og)Q3rcO9m4+rxt7zaSb|W4O+_xK&m{2T)L1MDX(QIw*rMZH*H2g2 zc-qM@E0sxg=309hPA7?%&ufgS*mgwMqb90YBz+}>oe`p6%0Z6nIU(K;l6?F{BNgjy zmSwJIG4C}rH_+TfOK;RL5VoDirrQTaJSq^Z%5e|fNSrtz#AA7!mO~4NNXW{@-fd{sN$o7vR_Uod z^9wkD#HIg(Uw}B}%89?g0WssWbgKf6Job1dJD=zc^TP$_ zAxv`*j39z$WI`iKanCX~aU#d=oy?thkw?jMB3$7e5#dSi(20EEZ<2272fR(Xw057w zBMED}pQ==iU&L7kUaLPE4{{+B%$gs7sHuJzHj+eCkcde~&<|8F;T*Y<+f<7@;fvN0 zfqH>hfVCysP3`wM^A^o{6xZC)0^kr3oJuMPI%PmE7Bq z7E0JG8^dfWH->StBb5l_A`Qi899}@%D`s`TIj~i79zaE7QPcgK7gJBu5(;^z1CgVM zFJYPt%orXZkR3RJ9k>;F3s+4^#Xi&bv`^l=IPgkZc_lk!`hMjI=LY%cRNg0$e79pG zE<|2K#GQ{+uIZGX@D8yx`~!ElQ#mO709f-95Z%;E!fMX9L3Hy>uK*Uk3}*kQL%RiK*`y{)cq39s9!NtG#Ml}L?!QTuUG0CyS*sl7s{s)XDH^xRmFP3`aO z;To-4##3qdaK^Df)DfxR5L7%K=F^g^T=7PhChj+mmAEmB__H8H0^m7E&)ft<7^Cc$ z@44(MZPQgdT^dlI{av8u^>YHDSU7Fxp~5K1m~Ik{j_tf-YOi$xj%{NtRRXxN{<0>7Me?6W)My zT7SZ6(xJwERAyBPiuYMuOEm?pwKq)rm5>RVTy8E*cA@LfK}Zr(K|&_Epg&Q;1fM&h z)3^#<>B*M#<(g~{`E4RRuxJT??wrzaU95@roP>c`2Oxn78pq#MU+Sv707S`QmWzae zk7Yw0`=`(~(leto5<$cRazXpbgOv_5j+XgQwP12V6k5I5?>*8|>0VNmWu=tN%EKxb zy}c@#6tb*!@8Nj7C!h36`E*O#@O#a$b&yZw)|a6G4?xVw2tvxpjQv8trzgCoBR1nHozb7^*JQaq zAj${h4!1EXybaT;t7rtgbsU8tj1#L=8}I08Gb*Vkd2PvP#5x)Q?j+9hC<W zdPW-l&M5Nw1AFWMhnN``%+jYeK!?m>-H(kUb6LRpHCR#@*d@Z++j*wzq$LgEemaFHawLzoP86Iy+*Rdjj3rZZ-AojfFP_* z1IAu@E{@(y&vU4c-(Sa@R}sHLG8PV3DPejkbuIH-p-uX&A$_P)&K*2S~~C zLs+`Htd|!3&IVRf85c5&OgoB-N=$OpgD84DiSuShVZtb^HuVMw6983pmK3r+tc1ce zR_~#B9|dNIBj?{EoV_`RN$WXYPTe$)N>_1Am9_g+uX zZZ`}(fB)NGgM862{y~kW9~+J9DEVJNLL+3c;ZKi6Vo$U#j`vMJTtZv$8$8# ziff7Mxw>x0^`zl7blr)YNy}^LdM$1z9j~M7ZakCBdb7ID;yG{b$3|EW8xM@IA)G_g zn-`bD=02By#yYKdAzAbmPi4iTm_IZQnYSdC#oXRyv2n?`r$G0tIZ zcbT$pr%{%oQrq0=Z)c%E)w&Vv(6_M}rBT{{JBVYnT;jej(jn?~s&CyBX+a&te5Vje zk0yQksiAQlCI1tMFi2G%51D87p|U?P&l0w9L`}Fme8pIS${w-M3hf7$R~HS@6s^6s zF165kWQDcRePH2O9rW3Fk9%z~gR%8TcGw7;T4ELwbLgKBTaP#$U2`w#aSP!Lc(dW0 z<}HF(%`+d)gMTSp(EO!vF<*=7Iy*9=PB#yVp`@CV4{00} z>_`D#+GEh-@l!{I%1=@9UxW0Gu~`@e+k=&Vvpcp5^T;Z#2@BbZF}6#7WQV+P_G(A& z#F+50qd8{jfLAA_KV-|qua)+uap%g|EzP3_W^0yi>69?QLvsT3+5Wz!o!!q7mk&8xon{++aTSRva*3lC7nnu zZGC)e>%;Ah_x!C5RNmRT>3^_s>uqI`LX{O~L8#2WD3lvQ)kTyDnz0bdE;7ul>ck2~ zTm;Gq2ADQfogVP&@5n6iwN>iM%0;}RtU&g2cdj)N}^ z`6A@M3f6DuLgwqeEKM^hMU?Jlak##j^@a)jBVXSb3^v8R^;F!yrj>HAUVW~89uLK} z&x77RcDNo4qV;g6zkVKPYl8#j`hE{S==-Z!Fux39FpIZXhtIRi++{A~^#1p%Gg^6O z{PEbXC11e6)6+-_c|DV6n?6)0<_(nm1CYWf5ieGV!a+lGMdJ%COY>|{R&rp&0C)pzt{Kkp_6gD3^Kt^j@S!n=+HF`lZ80H&N1ga5=yeISB3^3^c1`>XSr~|*s zy(SqpzY%A-2)mYA&;~e>@pnl`&1tTd^m|%VH8kpc4GS0gt!m9)mdP+mX-?(FSf?0< zLe=9e?N`+%da*b~v%KrbSD*ydtoG^$v{^?`)XU`>dgMBFTQKtw+O>$I0lcWuRtcVh zPF>P$l0Id2&GS|vP-$ezm#FqNkZ+^p&x078Glw;p1?rH=|6@6gs{KFyuhG|^G5$H& z+p>#E=HNK{5-jA3EKg*2^ZLcETq73=bG49>uqDr)*)pj$ZGN7}eNoQqrNwIBwtlZP@~X>)Wb!UxkC>5c)L_9x_(w^{>C`_aNZXuUw z$O=ez<{}VySEh&Zi_F)^_2B{oxQNKT=d^zv3!LwDryaRBbH1xx9eyg{HRmIjt6-=c89I_9DH0{*>o(HSK=CYE_M^p9X_FNmu>=+jz{0m>?b>B#(6l zrhRw0vM87;CyCNwA-ay@5|MFd7{^Ia^ma9Xltm|2W<&<*%WOF4+LH9IY%QehDj?UY zNpjDQa(|GVx7r3q9H8V2Acon3Rb1YI%{r*Ta!*DtKVyRnFpWJ=BfpN>endh0SyRwq z$yc-~$Yl{lUMxH_rqb$d(EcXz`z)i<3&>LD0>Aq~k>vu&E7rO__qIGb4 zxrwQCg1`;U)@vh}Yxi`i#{mK3L#p2fQL|N+I^FAewR{Isze347AO1+Rizp>aZpUU1a$on3 zvQ(^{bd6>=65OzR!8(L|66)5JH8?0nmA{d+d>>~(0?!a;qmihvS(~n0-T(BSp#Kwv zC|-=tYy3K<`5j75u4>d8Z1mEFz|)hR1lTk8aB?0h8eKGv{=eeeI(+N(x=cO?{<*bP zQ{Q@`L4n_|-=ja*24kb=p4q`ug4_5N!EKPPP`7ME^vT4)OcBQf0Px!BtLiFu>)lI_*c`cr&OwYM|=kzv4 zM#>g`-+%w_#-FDx>pwKu|MM{T6>jwy4YxRptssB0fSIu!*yia34xV=GcD%r2IgcOs zpgD1&Qw)lR=EkK?IVhXH7gsvfplbSlJkl8rMoqsEk9BH6&GcD3ZsZr^iOys&X=tT* zsxuu-n|?W-=^P1;n0_Tb+L;YzO}`o+>l_b`GwVZ(kMPm^79SP0O(!@Zp5tS;ZTSfM zs>O4ilfg-5iId_4ALkQWY?B4^;(5VVPl@?Wi%;^YEqjweBD-eQryoKG)^eTc;wRlK zN|Q#6zI(CJ5_p#AdHJqLGGd^?$0F%bpPpAT(Luj(snuRiIq?@h6?b|<5qIUH6dVhq zMyxQbT}f2dh-1Mobh{tK=^AL>tzHy!bV|R@vhHOmQ`w~5?Em=~{0g`F7aC!aeW1HJ z;p7*0zje9C{rg_v@&Yg3w*pW2qQFbv+Pusw_uZff&XOq4TD%Gx>CFu)pg6OZs2chh z=rukL`UrTdVsv#3+!LTeSD@C!IQS>{G$=Ft$bALUHnJ7HUrtULZ#J4Mf`>+&AvGx~c7N2j zabLu(PM{gEffZT<8!a0+XzhWE7SVyPWXp9|j)O~1P&0`}*K*0`HAa5j)2j0yi z0{1^Hc?6F~V)2F&QY|*qBuS+dQL>W8eDPA+>~-KuY7xG3N!(pb#M)V-srAMDarRc+ z6K8KVnotg3Y;>bV-fAx%ruv2Mx-N!cltft=9>Ysj5sg*w8D0J74qda$-(bFZ4GuP*62M#oBFYDs2Yt()1rd^R3WowrlMZ0m@;M#9=LFan3 zi9iioGVO#kTQA5NY)`ul*;d-Wy+%(-->z9pj}XhA-|5Pe;kq$O-W(-v=&gwm4#Sr* zQdKk-bL}b{wTo=Y{#o7SYwEUqk%ahCWIdTQ$-Q=e)F;R`?)CvXP@_>BS{tE=P$jkI zH12Lu&jZ~DJ?|A}Eu(J6i?bF~UfS}Zw(_!elSW7AaPjv;qY5LhaC z1~c+3O}p^i9^%8H!JM&7&XXIcZQvm^A*|#GF-;mKTDH%Iwy~j6;Q_PAlUQ^Pw<051 z%!VJAnalj2T&KL#v;W(F;ZV;$;3>9z6(S_r_bCI7g0N?x>xcr*+H3QFo{q-D0&~OavYTIVjGg0&R{ZZ4ul#O$r7X}V5ZaIiLe>kyiZ4?HD;pDcp zIt97Y1NM!@OWz?+Y}vyZ%oYd5;S{g5E$qE?(@NfTt?bAHwm#3S4P^KJsqE;$!kX%! z{EhXULpii<-L!n`b42h)`K$7^o#&FZL}*YMlo0bTZd4KPvxDk3+ZY*)aI422F!}1B zGCVdIq3?gd?qCFW9a-+bxq?in&Mz*mt*tHOz5z5-3(d5%*yv>|i~A5?EfDypNxUw{ z$Z7;m@MyOS&(>@$@N=gM?7MG(T-ldP`tI`=&R?wC!N@*wSGe|MBWVlm-)#U+Ro^~8 zUq7zxR6!=C5ntSk6r3A1l_g7Pf>{GHw6Ro1edLoK@H=cpvCtlbL_8>nd))|GEEtiZ zC52kaQ%+FW8A^gsNxl^vHNu*4ga`_izkbo}l3Mr3cf%ev9lnGtP zQiF4{LQ<;xK1s>9iO*?^%%mNS-H2unxt737dtDhNnJ#r3Qi)KeYjO_c`e+_%6baB_ z6IiRXN7_??+d}1{J(Jsl0_`Pgioi39CS+5+INNzAHYu0P+aXwcCgu-D?rWHT54WOi zTCR_%9R-?}Sq*KKRqauB#aoMfeH8#PH*%%?V%i!z3{_~Ojvq%Fp{t&Nx zHqt5clSvE|{{klby z)E!E9Nt#6D9URcD$533!EX<+~DpBCR9zjxkGOU-h6N^L_iJ6$RYnF|W`$Z~krLxn= zbonz10#lUga_%9-EM3}z!Rd*;H-re!TTvo--OGK)==OKu49ZV7gA~5AkAr^jJ~-RB z75SgVs>rG~n}P=v;Q^ztIE`(x_mBO#L#@5%!gX60XQw#{|$r88p5^% z2E+sYswra!^o9<%w_$f+a6SUMW8|6{s%gC2_J-F#k-4iL>OoH~b3d_p;fMu`7PsIN z*HF=9{`y~moj$PhL{>1^3A`oTyqROB{}s+s1~t(m)T9y8cK_7rFP6@}^W}S{t)I_F z`C$I^7ZIM+tExM zwC!~)+Q4nfXbU!VVNF;Smu=$Req8iu)Ia%uEKA#hX~#`d+!A%_#4}0^YJNtcXUOt*mR8-UGZ? zfhbe)AengP-F3$Rrld2PB;Wvn+*q!n(Y1qnJ*b)2!Sw*QqUyr3T~v{#3V&So`lk=o z#o>h!#hLsgnx{&Zr_?|NP44OhSh-8XbxpBC&cDru{l;?MTpPEut&1WlO z{zekrn?HTy`o;SEUfj>8Ei<nCb=O-1V%V2QzWYKj0fsl~NW&s?FWCu=8eJ+S?Osu%{4hXZ?d(zX37d z;#LG?R-w%LuN;s`P9&>AEEOJ)$w7d%VF3_c@KqssHz;Hh6*W)3*YxP4P4b|Qq6(wvnUmfRwn2EH{14S?u~Ms{ivO{C`smEeq|GKO zHk&EsNeAE9X+%jFA_}WU#p~D8k8p(RpE-A>(-PNjTzKosxf{36HX3)^S8l%f`qkDi zE`Rp^TkpKlI``T;Z!BNEcJWjGW)t=pr@at-LXsaP^j}T=h5R$=#4q2W=J#l{pRIRs z>dBQ66J;Dl=>mMcff=!I5y#E^d^7V5{W6`0j6(JFGm{_VjAL|nV2y1Fr~!x+QPnCc&Cuf z=R=wf4O1N5TA*q#uT6K>V<&liy1*k;hNwi>^QvKy-qg|n|=Oc;-&a*?$*Dg&{I@iQe{Y2)h<%Sm|y$*PVGg`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10UKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 sXQ$?+=oc3y>!+lp>&M4u=4F<|$LkeT-r}&y%}*)KNwou6`5A~A0D-+PB>(^b diff --git a/api/dfg/__pycache__/__init__.cpython-39.pyc b/api/dfg/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 01158117b3d6b8174e71d01bd84cb547cc0d75d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180 zcmYe~<>g`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10UKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 sXQ$?+=oc3y>!+lp>&M4u=4F<|$LkeT-r}&y%}*)KNwou6`5A~A0E#UyF#rGn diff --git a/api/migrations/__pycache__/0001_initial.cpython-39.pyc b/api/migrations/__pycache__/0001_initial.cpython-39.pyc deleted file mode 100644 index 4e30283f452f34d45038d9ce13a22f9a69ad38c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 844 zcmYjPJ#*AB5S3-;K7E(KP*Fj{%oT}4&oBwO38Y9GLgPlG^KKknY}t`~fun^s{{c$= z2Q@uvsqz<604v`?B8^sWCC%>Jx00jLfYF%!_%45P#(sI{{)ougqh+6w0R}=|NJp?qM7Lc3_#eYl_wdw6ZDr>9zEQTR%+hfMhmIdjCy|TgqNzk?P&No| zSQ}MJvlJJ&bbX6P$wGZZ7hj>7RThPoCA!Gl8=%T20jVKBhyr!qoV#%*b){7%3jq|* zZQ4efMl`BKLS^knsNtUbpt<8FvTC852{$ptb)7SX1=y3x*!4u4RR}i_g5+KlNH`Ya zav=-PguJfOsv)i}2COLu>P(?E)OzNI2W@f3zELLhC-Ezl?^D@_K8NccoJ+HYQ)0b_ zr$l-M<0IW*j_Wsrs>XHqzcKTK;R@tIN-PmNmuBUjF zXneoz#Wu@iwQbLe1#Zt|c0t4wS*zskeg`kg8KZ!i6Hthh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10DKeRZts8~Na zKQAx8s3uwM;)RwOltfu_U#qQXfL;Cgqf->Lw*7 zXQ$?+=oc3y>*r>s7bTWt=I0gb$H!;pWtPOp>lIYq;;_lhPbtkwwFBAw8HgDG0gEz3 diff --git a/transactions/migrations/__pycache__/0001_initial.cpython-39.pyc b/transactions/migrations/__pycache__/0001_initial.cpython-39.pyc deleted file mode 100644 index 04dc757209a5657963a081c46b5bcb54d97e58ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 853 zcmYjPy>iqr5SC=;etf=5V5q2|Vdjd&p=X$c+yqi24WV(P(ODZu7h5)xFL1O_a_;~o zZ^A2}mMX&=qySdFgPb&4eJg2pzx`HXIE)C6U!T8=AB>Qn-q{=>ItRGy6Eu=YT9A^a zv}7rx!1E(c<%0>8=CzEmLVli(jo*57g zNH?gBDur3_D_FR`1*1fvK7fm_!AvU)T#FK1=bI&UuBC=Jo)>^P z=KSqk6rKrsU8PlnTwN@o#vGtC1=e8ei5ncX#hv;_nZTdKFI0YrWgq(-u77kX%o+XLe<`Kqg&25>yLA`8}Ra#fZfYO&$A(Kg!%}daYO%CgN z0@sO#ce`F}vq)Ck?zEW0?o?z~NIcn4#3pwiE!p4Sf6CWY?$rz9e{ctveTb&VLKcQ0 zW$XbR(P%Yq+3)aA`|b?;+?O+<^J-5{TUA;i+RM8VR_HOtbG>IPp`Uxbte0^o_zQf4 B`g`kf~#F85<&E15P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;x_!erR!OQL%n< zeqLUFQBi7UUPgXSioQ#Ja%paAUP-aOV?lvSYMFjsYPoJ|Vo7RIr9OnxP0A@v)lEuF z&Q8rs(Jv`V%qvbzF3HT#E7s4=OfO1=FyrGh^D;}~ Date: Mon, 27 Dec 2021 17:08:39 -0500 Subject: [PATCH 012/305] Bug fixing and wait-time stuff --- api/dfg/CalculateWaitTimes.py | 35 ++++++++++++++++++ api/dfg/ExternalEateries.py | 7 ++-- api/views.py | 11 ++++-- .../controllers/mock_vendor_controller.py | 4 +- .../update_transactions_controller.py | 23 ++++++------ ...ry_transaction_name_204126_idx_and_more.py | 21 +++++++++++ transactions/models.py | 3 +- .../scripts/ingest_log_transactions.py | 37 +++++++++++++++++++ transactions/serializers.py | 19 ++++++++++ transactions/urls.py | 4 +- transactions/views.py | 12 +++--- 11 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 api/dfg/CalculateWaitTimes.py create mode 100644 transactions/migrations/0003_remove_transactionhistory_transaction_name_204126_idx_and_more.py create mode 100644 transactions/scripts/ingest_log_transactions.py create mode 100644 transactions/serializers.py diff --git a/api/dfg/CalculateWaitTimes.py b/api/dfg/CalculateWaitTimes.py new file mode 100644 index 0000000..1451219 --- /dev/null +++ b/api/dfg/CalculateWaitTimes.py @@ -0,0 +1,35 @@ +from api.datatype.Cafe import Cafe +from api.datatype.DiningHall import DiningHall +from api.dfg.DfgNode import DfgNode +from transactions.models import TransactionHistory +from transactions.serializers import TransactionHistorySerializer + +import datetime + +class CalculateWaitTimes(DfgNode): + + def __init__(self, child: DfgNode): + self.child = child + + def __call__(self, *args, **kwargs): + eateries_with_wait_times = [] + past_days = [] + # kwargs.get("start") + for i in range(1, 13): + past_day = datetime.date.today() - datetime.timedelta(days = 7 * i) + past_days.append(past_day) + + transaction_data = TransactionHistory.objects.filter(canonical_date__in=past_days) + # print(len(transaction_data)) + # print('--------------------') + # print(transaction_data) + for eatery in self.child(*args, **kwargs): + eateries_with_wait_times.append(eatery) + + return eateries_with_wait_times + + def children(self): + return [self.child] + + def description(self): + return "CalculateWaitTimes" diff --git a/api/dfg/ExternalEateries.py b/api/dfg/ExternalEateries.py index 6f8dde0..a8a8c46 100644 --- a/api/dfg/ExternalEateries.py +++ b/api/dfg/ExternalEateries.py @@ -82,11 +82,11 @@ def cafe_events_from_json( start_weekday, end_weekday = weekdays.split("-") weekdays = range( ExternalEateries.WEEKDAYS.index(start_weekday), - ExternalEateries.WEEKDAYS.index(end_weekday) + ExternalEateries.WEEKDAYS.index(end_weekday) + 1 ) else: - weekdays = [weekdays] + weekdays = [ExternalEateries.WEEKDAYS.index(weekdays)] for weekday in weekdays: for event_template in event_templates: @@ -94,7 +94,6 @@ def cafe_events_from_json( weekday_to_event_templates[weekday] = [] weekday_to_event_templates[weekday].append(event_template) - resolved_events: list[CafeEvent] = [] current = start_date while current <= end_date: @@ -125,7 +124,7 @@ def time_since_midnight(time_str: str) -> datetime.time: if not match: return datetime.time() - hours = int(match.group(1)) + hours = int(match.group(1))%12 minutes = int(match.group(2)) is_pm = match.group(3) == "pm" return datetime.time( diff --git a/api/views.py b/api/views.py index 78304a1..6c36a65 100644 --- a/api/views.py +++ b/api/views.py @@ -2,6 +2,7 @@ import pytz from django.http import JsonResponse +from api.dfg.CalculateWaitTimes import CalculateWaitTimes from api.dfg.Concat import Concat from api.dfg.CornellDiningNow import CornellDiningNow @@ -14,10 +15,12 @@ dataflow_graph = DictResponseWrapper( EateryToJson( EateryGroupByType( - Concat([ - CornellDiningNow(), - ExternalEateries() - ]) + CalculateWaitTimes( + Concat([ + CornellDiningNow(), + ExternalEateries() + ]) + ) ) ), re_raise_exceptions=True diff --git a/transactions/controllers/mock_vendor_controller.py b/transactions/controllers/mock_vendor_controller.py index e943dcf..37a0479 100644 --- a/transactions/controllers/mock_vendor_controller.py +++ b/transactions/controllers/mock_vendor_controller.py @@ -21,8 +21,8 @@ def process(self): "CROWD_COUNT": crowd_count }) - return JsonResponse({ + return { "TIMESTAMP": recent_timestamp.strftime('%Y-%m-%d %I:%M:%S %p'), "LOOK_BACK_MINUTES": 5, "UNITS": units - }) + } diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index c019874..a9a2e26 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -1,8 +1,7 @@ from django.contrib.auth.models import User from django.core.paginator import Paginator from django.db.models import Q -from django.http import JsonResponse -from datetime import datetime +from datetime import datetime, timedelta from random import randrange from transactions.models import TransactionHistory @@ -85,20 +84,22 @@ def __init__(self, data): def process(self): if "error" in self._data: - return JsonResponse({ + return { "success": False, "result": None, "error": self._data["error"] - }) + } if self._data["TIMESTAMP"] == "Invalid date": - return JsonResponse({ + return { "success": False, "result": None, "error": "Invalid date" - }) + } recent_datetime = datetime.strptime(self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p') - recent_date = recent_datetime.date() - recent_time = recent_datetime.time() + canonical_date = recent_datetime.date() + if recent_datetime.hour < 4: + # between 12am and 4am associate this with the previous day + canonical_date = canonical_date - timedelta(days=1) num_inserted = 0 ignored_names = set() for place in self._data["UNITS"]: @@ -108,15 +109,15 @@ def process(self): else: num_inserted += 1 try: - TransactionHistory.objects.create(name = name, canonical_date = recent_date, timestamp=recent_time, transaction_count=place["CROWD_COUNT"]) + TransactionHistory.objects.create(name = name, canonical_date = canonical_date, timestamp=recent_datetime, transaction_count=place["CROWD_COUNT"]) except: num_inserted -= 1 - return JsonResponse({ + return { "success": True, "result": { "num_inserted": num_inserted, "ignored_names": list(ignored_names) }, "error": None - }) + } diff --git a/transactions/migrations/0003_remove_transactionhistory_transaction_name_204126_idx_and_more.py b/transactions/migrations/0003_remove_transactionhistory_transaction_name_204126_idx_and_more.py new file mode 100644 index 0000000..183afed --- /dev/null +++ b/transactions/migrations/0003_remove_transactionhistory_transaction_name_204126_idx_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0 on 2021-12-27 15:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0002_rename_end_timestamp_transactionhistory_timestamp_and_more'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='transactionhistory', + name='transaction_name_204126_idx', + ), + migrations.AddIndex( + model_name='transactionhistory', + index=models.Index(fields=['canonical_date'], name='transaction_canonic_a40422_idx'), + ), + ] diff --git a/transactions/models.py b/transactions/models.py index 95c6c3a..c141803 100644 --- a/transactions/models.py +++ b/transactions/models.py @@ -6,8 +6,7 @@ class TransactionHistory(models.Model): class Meta: unique_together = ('name', 'timestamp', 'canonical_date') - # idea is when fetching wait time for [name] in between [timestamp - 5, timestamp], we can look at the last 2 weeks of data for that [name, timestamp] pair - indexes = [models.Index(fields = ['name', 'timestamp', 'canonical_date'])] + indexes = [models.Index(fields = ['canonical_date'])] name = models.CharField(max_length=100) canonical_date = models.DateField() timestamp = models.TimeField() diff --git a/transactions/scripts/ingest_log_transactions.py b/transactions/scripts/ingest_log_transactions.py new file mode 100644 index 0000000..ca4de15 --- /dev/null +++ b/transactions/scripts/ingest_log_transactions.py @@ -0,0 +1,37 @@ +# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db + +import requests +import json +import time +from django.http import JsonResponse + +from transactions.controllers.update_transactions_controller import UpdateTransactionsController +from ..models import TransactionHistory + +def ingest(request): + num_deleted = TransactionHistory.objects.all().delete()[0] + counter = 0 + num_inserted = 0 + with open("static_sources/data.log", "r") as log: + for line in log: + try: + data = json.loads(line) + timestamp = time.strptime(data['TIMESTAMP'], '%Y-%m-%d %I:%M:%S %p') + if counter % 100 == 1: + print(timestamp) + if timestamp.tm_year == 2021: + counter += 1 + res = UpdateTransactionsController(data).process() + if res["success"]: + num_inserted += res["result"]["num_inserted"] + except: + # Reading a blank line or INVALID DATE + pass + return JsonResponse({ + "success": True, + "result": { + "num_inserted": num_inserted, + "num_deleted": num_deleted + }, + "error": None + }) \ No newline at end of file diff --git a/transactions/serializers.py b/transactions/serializers.py new file mode 100644 index 0000000..91eff4a --- /dev/null +++ b/transactions/serializers.py @@ -0,0 +1,19 @@ +import json + +from rest_framework import serializers +from .models import TransactionHistory + + +class TransactionHistorySerializer(serializers.ModelSerializer): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + class Meta: + model = TransactionHistory + fields = ( + "name", + "canonical_date", + "timestamp", + "transaction_count" + ) + read_only_fields = fields \ No newline at end of file diff --git a/transactions/urls.py b/transactions/urls.py index d4c7462..f79f2eb 100644 --- a/transactions/urls.py +++ b/transactions/urls.py @@ -1,8 +1,10 @@ from django.urls import path from . import views +from transactions.scripts import ingest_log_transactions urlpatterns = [ path("fetch_recent_transactions", views.autofetch_recent_transactions, name="autofetch_recent_transactions"), - path("mock_vendor_endpoint", views.mock_vendor_endpoint, name="mock_vendor_endpoint") + path("mock_vendor_endpoint", views.mock_vendor_endpoint, name="mock_vendor_endpoint"), + path("ingest_log_transactions", ingest_log_transactions.ingest, name="ingest_log_transactions") ] diff --git a/transactions/views.py b/transactions/views.py index 4b2654d..e4f309f 100644 --- a/transactions/views.py +++ b/transactions/views.py @@ -8,14 +8,14 @@ import os def mock_vendor_endpoint(request): - return MockVendorController().process() + return JsonResponse(MockVendorController().process()) # Called every 5 minutes, updates TransactionHistory database def autofetch_recent_transactions(request): - endpoint = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" - # endpoint = "http://localhost:8000/mock_vendor_endpoint" + # endpoint = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" + endpoint = "http://localhost:8000/mock_vendor_endpoint" headers = CaseInsensitiveDict() - token = os.environ.get("CORNELL_VENDOR_TOKEN") + "sss" + token = os.environ.get("CORNELL_VENDOR_TOKEN") api_key = os.environ.get("CORNELL_VENDOR_API_KEY") headers["Accept"] = "application/json" headers["Authorization"] = "Bearer {}".format(token) @@ -23,7 +23,7 @@ def autofetch_recent_transactions(request): resp = requests.get(endpoint, headers=headers) if resp.status_code == 200: - return UpdateTransactionsController(resp.json()).process() + return JsonResponse(UpdateTransactionsController(resp.json()).process()) else: - return UpdateTransactionsController({"error": resp.json()}).process() + return JsonResponse(UpdateTransactionsController({"error": resp.json()}).process()) \ No newline at end of file From f8ee7b45ff33d4e57e7ed1810d5f558c29576dfb Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Tue, 28 Dec 2021 11:58:35 -0500 Subject: [PATCH 013/305] Combine cafes and dining halls --- api/datatype/Cafe.py | 51 -------- api/datatype/CafeEvent.py | 11 -- api/datatype/CafeMenu.py | 10 -- api/datatype/DiningHall.py | 47 ------- api/datatype/DiningHallEvent.py | 33 ----- api/datatype/DiningHallMenu.py | 10 -- api/datatype/Eatery.py | 30 ++++- api/datatype/Event.py | 14 +- api/datatype/Menu.py | 9 ++ ...ingHallMenuCategory.py => MenuCategory.py} | 3 +- api/dfg/CalculateWaitTimes.py | 2 - api/dfg/CornellDiningNow.py | 110 ++++++---------- api/dfg/EateryGroupByType.py | 34 ----- api/dfg/ExternalEateries.py | 57 ++++---- api/dfg/GoogleSheetsEateries.py | 53 ++++---- api/views.py | 13 +- static_sources/external_eateries.json | 123 ++++++++++++------ 17 files changed, 231 insertions(+), 379 deletions(-) delete mode 100644 api/datatype/Cafe.py delete mode 100644 api/datatype/CafeEvent.py delete mode 100644 api/datatype/CafeMenu.py delete mode 100644 api/datatype/DiningHall.py delete mode 100644 api/datatype/DiningHallEvent.py delete mode 100644 api/datatype/DiningHallMenu.py create mode 100644 api/datatype/Menu.py rename api/datatype/{DiningHallMenuCategory.py => MenuCategory.py} (91%) delete mode 100644 api/dfg/EateryGroupByType.py diff --git a/api/datatype/Cafe.py b/api/datatype/Cafe.py deleted file mode 100644 index d7c25fc..0000000 --- a/api/datatype/Cafe.py +++ /dev/null @@ -1,51 +0,0 @@ -from datetime import date -from typing import Optional - -import pytz - -from api.datatype.Eatery import Eatery -from api.datatype.Event import filter_range -from api.datatype.CafeEvent import CafeEvent -from api.datatype.CafeMenu import CafeMenu - - -class Cafe(Eatery): - - def __init__( - self, - name: str, - campus_area: str, - events: list[CafeEvent], - latitude: float, - longitude: float, - menu: CafeMenu - ): - super().__init__(name=name) - self.campus_area = campus_area - self.latitude = latitude - self.longitude = longitude - self.known_events = events - self.menu = menu - - def events( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None - ): - return filter_range(self.known_events, tzinfo, start, end) - - def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None - ): - return { - "campus_area": self.campus_area, - "events": [event.to_json() for event in self.events(tzinfo, start, end)], - "latitude": self.latitude, - "longitude": self.longitude, - "name": self.name, - "menu": self.menu.to_json() - } diff --git a/api/datatype/CafeEvent.py b/api/datatype/CafeEvent.py deleted file mode 100644 index efbbf5b..0000000 --- a/api/datatype/CafeEvent.py +++ /dev/null @@ -1,11 +0,0 @@ -from api.datatype.Event import Event - - -class CafeEvent(Event): - - def to_json(self): - return { - "canonical_date": str(self.canonical_date), - "start_timestamp": self.start_timestamp, - "end_timestamp": self.end_timestamp - } diff --git a/api/datatype/CafeMenu.py b/api/datatype/CafeMenu.py deleted file mode 100644 index 5d9ab36..0000000 --- a/api/datatype/CafeMenu.py +++ /dev/null @@ -1,10 +0,0 @@ -from api.datatype.MenuItem import MenuItem - - -class CafeMenu: - - def __init__(self, items: list[MenuItem]): - self.items = items - - def to_json(self): - return [item.to_json() for item in self.items] diff --git a/api/datatype/DiningHall.py b/api/datatype/DiningHall.py deleted file mode 100644 index df81976..0000000 --- a/api/datatype/DiningHall.py +++ /dev/null @@ -1,47 +0,0 @@ -from datetime import date -from typing import Optional - -import pytz - -from api.datatype.Eatery import Eatery -from api.datatype.DiningHallEvent import DiningHallEvent -from api.datatype.Event import filter_range - - -class DiningHall(Eatery): - - def __init__( - self, - name: str, - campus_area: str, - events: list[DiningHallEvent], - latitude: float, - longitude: float, - ): - super().__init__(name=name) - self.campus_area = campus_area - self.latitude = latitude - self.longitude = longitude - self.known_events = events - - def events( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None, - ): - return filter_range(self.known_events, tzinfo, start, end) - - def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None - ): - return { - "campus_area": self.campus_area, - "events": [event.to_json() for event in self.events(tzinfo, start, end)], - "latitude": self.latitude, - "longitude": self.longitude, - "name": self.name, - } diff --git a/api/datatype/DiningHallEvent.py b/api/datatype/DiningHallEvent.py deleted file mode 100644 index d0d1e83..0000000 --- a/api/datatype/DiningHallEvent.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Optional - -from api.datatype.Event import Event -from api.datatype.DiningHallMenu import DiningHallMenu -from datetime import date - - -class DiningHallEvent(Event): - - def __init__( - self, - description: str, - canonical_date: date, - start_timestamp: int, - end_timestamp: int, - menu: Optional[DiningHallMenu] - ): - super().__init__( - canonical_date=canonical_date, - start_timestamp=start_timestamp, - end_timestamp=end_timestamp - ) - self.description = description - self.menu = menu - - def to_json(self): - return { - "description": self.description, - "canonical_date": str(self.canonical_date), - "start_timestamp": self.start_timestamp, - "end_timestamp": self.end_timestamp, - "menu": self.menu.to_json() if self.menu is not None else None - } diff --git a/api/datatype/DiningHallMenu.py b/api/datatype/DiningHallMenu.py deleted file mode 100644 index 596a2bc..0000000 --- a/api/datatype/DiningHallMenu.py +++ /dev/null @@ -1,10 +0,0 @@ -from api.datatype.DiningHallMenuCategory import DiningHallMenuCategory - - -class DiningHallMenu: - - def __init__(self, categories: list[DiningHallMenuCategory]): - self.categories = categories - - def to_json(self): - return [category.to_json() for category in self.categories] diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index 5c57a2c..d19987a 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -3,19 +3,43 @@ import pytz +from api.datatype.Event import Event, filter_range class Eatery: def __init__( self, - name: str + name: str, + campus_area: str, + events: list[Event], + latitude: float, + longitude: float, ): self.name = name + self.campus_area = campus_area + self.latitude = latitude + self.longitude = longitude + self.known_events = events + + def events( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None, + ): + return filter_range(self.known_events, tzinfo, start, end) def to_json( self, - tzinfo: pytz.timezone, + tzinfo: Optional[pytz.timezone] = None, start: Optional[date] = None, end: Optional[date] = None ): - raise Exception() + return { + "campus_area": self.campus_area, + "events": [event.to_json() for event in self.events(tzinfo, start, end)], + "latitude": self.latitude, + "longitude": self.longitude, + "name": self.name, + } + diff --git a/api/datatype/Event.py b/api/datatype/Event.py index caccc0c..9083959 100644 --- a/api/datatype/Event.py +++ b/api/datatype/Event.py @@ -2,6 +2,8 @@ from datetime import date, datetime, time +from api.datatype.Menu import Menu + import pytz @@ -9,16 +11,26 @@ class Event: def __init__( self, + description: str, canonical_date: date, start_timestamp: int, end_timestamp: int, + menu: Menu ): + self.description = description self.canonical_date = canonical_date self.start_timestamp = start_timestamp self.end_timestamp = end_timestamp + self.menu = menu def to_json(self): - raise Exception() + return { + "description": self.description, + "canonical_date": str(self.canonical_date), + "start_timestamp": self.start_timestamp, + "end_timestamp": self.end_timestamp, + "menu": self.menu.to_json() + } def __contains__(self, item: int): return self.start_timestamp <= item <= self.end_timestamp diff --git a/api/datatype/Menu.py b/api/datatype/Menu.py new file mode 100644 index 0000000..dd0f742 --- /dev/null +++ b/api/datatype/Menu.py @@ -0,0 +1,9 @@ +from api.datatype.MenuCategory import MenuCategory + +class Menu: + + def __init__(self, categories: list[MenuCategory]): + self.categories = categories + + def to_json(self): + return [category.to_json() for category in self.categories] diff --git a/api/datatype/DiningHallMenuCategory.py b/api/datatype/MenuCategory.py similarity index 91% rename from api/datatype/DiningHallMenuCategory.py rename to api/datatype/MenuCategory.py index d00e7d5..3a2fee3 100644 --- a/api/datatype/DiningHallMenuCategory.py +++ b/api/datatype/MenuCategory.py @@ -1,7 +1,6 @@ from api.datatype.MenuItem import MenuItem - -class DiningHallMenuCategory: +class MenuCategory: def __init__(self, category: str, items: list[MenuItem]): self.category = category diff --git a/api/dfg/CalculateWaitTimes.py b/api/dfg/CalculateWaitTimes.py index 1451219..d955b40 100644 --- a/api/dfg/CalculateWaitTimes.py +++ b/api/dfg/CalculateWaitTimes.py @@ -1,5 +1,3 @@ -from api.datatype.Cafe import Cafe -from api.datatype.DiningHall import DiningHall from api.dfg.DfgNode import DfgNode from transactions.models import TransactionHistory from transactions.serializers import TransactionHistorySerializer diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index 8660a1d..41fdd4e 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -2,14 +2,11 @@ from api.dfg.DfgNode import DfgNode import requests -from api.datatype.DiningHall import DiningHall -from api.datatype.Cafe import Cafe -from api.datatype.CafeMenu import CafeMenu -from api.datatype.CafeEvent import CafeEvent +from api.datatype.Eatery import Eatery +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem -from api.datatype.DiningHallEvent import DiningHallEvent -from api.datatype.DiningHallMenuCategory import DiningHallMenuCategory -from api.datatype.DiningHallMenu import DiningHallMenu from datetime import date @@ -17,7 +14,7 @@ class CornellDiningNow(DfgNode): CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" - def __call__(self, *args, **kwargs) -> list[Union[Cafe, DiningHall]]: + def __call__(self, *args, **kwargs) -> list[Eatery]: try: response = requests.get(CornellDiningNow.CORNELL_DINING_URL).json() @@ -36,76 +33,26 @@ def __call__(self, *args, **kwargs) -> list[Union[Cafe, DiningHall]]: raise Exception(response["message"]) @staticmethod - def parse_eatery(json_eatery: dict) -> Union[Cafe, DiningHall]: + def parse_eatery(json_eatery: dict) -> Eatery: is_cafe = "Cafe" in { eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] } - - if is_cafe: - return CornellDiningNow.cafe_from_json(json_eatery) - else: - return CornellDiningNow.dining_hall_from_json(json_eatery) - - @staticmethod - def cafe_from_json(json_eatery: dict) -> Cafe: - return Cafe( + return Eatery( name=json_eatery["name"], campus_area=json_eatery["campusArea"]["descrshort"], - events=CornellDiningNow.cafe_event_from_json( - json_eatery["operatingHours"] - ), latitude=json_eatery["latitude"], longitude=json_eatery["longitude"], - menu=CornellDiningNow.cafe_menu_from_json( - json_eatery["diningItems"] - ) - ) - - @staticmethod - def cafe_event_from_json(json_operating_hours: list) -> list[CafeEvent]: - json_operating_hours = sorted( - json_operating_hours, - key=lambda json_date_events: json_date_events["date"] - ) - events = [] - - for json_date_events in json_operating_hours: - for json_event in json_date_events["events"]: - events.append(CafeEvent( - canonical_date=date.fromisoformat(json_date_events["date"]), - start_timestamp=json_event["startTimestamp"], - end_timestamp=json_event["endTimestamp"] - )) - - return events - - @staticmethod - def cafe_menu_from_json(json_dining_items: list) -> CafeMenu: - items = [] - - for json_dining_item in json_dining_items: - items.append(MenuItem( - healthy=json_dining_item["healthy"], - name=json_dining_item["item"] - )) - - return CafeMenu(items=items) - - @staticmethod - def dining_hall_from_json(json_eatery: dict) -> DiningHall: - return DiningHall( - name=json_eatery["name"], - campus_area=json_eatery["campusArea"]["descrshort"], - events=CornellDiningNow.dining_hall_events_from_json( - json_operating_hours=json_eatery["operatingHours"] + events=CornellDiningNow.eatery_events_from_json( + json_operating_hours=json_eatery["operatingHours"], + json_dining_items = json_eatery["diningItems"], + is_cafe = is_cafe ), - latitude=json_eatery["latitude"], - longitude=json_eatery["longitude"], + ) @staticmethod - def dining_hall_events_from_json(json_operating_hours: list) -> list[DiningHallEvent]: + def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: json_operating_hours = sorted( json_operating_hours, key=lambda json_date_events: json_date_events["date"] @@ -116,18 +63,37 @@ def dining_hall_events_from_json(json_operating_hours: list) -> list[DiningHallE canonical_date = date.fromisoformat(json_date_events["date"]) for json_event in json_date_events["events"]: - events.append(DiningHallEvent( - description=json_event["descr"], + events.append(Event( canonical_date=canonical_date, + description=json_event["descr"], start_timestamp=json_event["startTimestamp"], end_timestamp=json_event["endTimestamp"], - menu=CornellDiningNow.dining_hall_menu_from_json(json_event["menu"]) + menu=CornellDiningNow.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe) )) return events @staticmethod - def dining_hall_menu_from_json(json_menu: list) -> DiningHallMenu: + def eatery_menu_from_json(json_menu: list, json_dining_items: list, is_cafe: bool): + if is_cafe: + return CornellDiningNow.cafe_menu_from_json(json_dining_items) + else: + return CornellDiningNow.dining_hall_menu_from_json(json_menu) + + @staticmethod + def cafe_menu_from_json(json_dining_items: list) -> Menu: + category_map = {} + for item in json_dining_items: + if item['category'] not in category_map: + category_map[item['category']] = [] + category_map[item['category']].append(MenuItem(healthy=item['healthy'], name = item['item'])) + categories = [] + for category_name in category_map: + categories.append(MenuCategory(category_name, category_map[category_name])) + return Menu(categories=categories) + + @staticmethod + def dining_hall_menu_from_json(json_menu: list) -> Menu: json_menu = sorted( json_menu, key=lambda json_menu_category: json_menu_category["sortIdx"] @@ -140,12 +106,12 @@ def dining_hall_menu_from_json(json_menu: list) -> DiningHallMenu: for json_item in json_menu_category["items"] ] - menu_categories.append(DiningHallMenuCategory( + menu_categories.append(MenuCategory( category=json_menu_category["category"], items=items )) - return DiningHallMenu(categories=menu_categories) + return Menu(categories=menu_categories) def description(self): return "CornellDiningNow" diff --git a/api/dfg/EateryGroupByType.py b/api/dfg/EateryGroupByType.py deleted file mode 100644 index 5aa2c3e..0000000 --- a/api/dfg/EateryGroupByType.py +++ /dev/null @@ -1,34 +0,0 @@ -from api.datatype.Cafe import Cafe -from api.datatype.DiningHall import DiningHall -from api.dfg.DfgNode import DfgNode - - -class EateryGroupByType(DfgNode): - - def __init__(self, child: DfgNode): - self.child = child - - def __call__(self, *args, **kwargs): - eateries_by_type = { - "cafes": [], - "dining_halls": [] - } - - did_warn_about_non_eatery_type = False - - for eatery in self.child(*args, **kwargs): - if isinstance(eatery, DiningHall): - eateries_by_type["dining_halls"].append(eatery) - elif isinstance(eatery, Cafe): - eateries_by_type["cafes"].append(eatery) - elif not did_warn_about_non_eatery_type: - print("Non-eatery type encountered.") - did_warn_about_non_eatery_type = True - - return eateries_by_type - - def children(self): - return [self.child] - - def description(self): - return "EateryGroupByType" diff --git a/api/dfg/ExternalEateries.py b/api/dfg/ExternalEateries.py index a8a8c46..9220645 100644 --- a/api/dfg/ExternalEateries.py +++ b/api/dfg/ExternalEateries.py @@ -5,10 +5,10 @@ import pytz from api.dfg.DfgNode import DfgNode -from api.datatype.Cafe import Cafe -from api.datatype.DiningHall import DiningHall -from api.datatype.CafeMenu import CafeMenu -from api.datatype.CafeEvent import CafeEvent +from api.datatype.Eatery import Eatery +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem import json @@ -29,7 +29,7 @@ class ExternalEateries(DfgNode): 'sunday' ] - def __call__(self, *args, **kwargs) -> list[Union[Cafe, DiningHall]]: + def __call__(self, *args, **kwargs) -> list[Eatery]: eateries = [] with open(ExternalEateries.EXTERNAL_EATERIES_PATH) as f: @@ -39,7 +39,7 @@ def __call__(self, *args, **kwargs) -> list[Union[Cafe, DiningHall]]: end = kwargs.get("end", start + datetime.timedelta(days=7)) for json_eatery in json_eateries: - eateries.append(ExternalEateries.cafe_from_json( + eateries.append(ExternalEateries.eatery_from_json( json_eatery, start=start, end=end @@ -48,30 +48,29 @@ def __call__(self, *args, **kwargs) -> list[Union[Cafe, DiningHall]]: return eateries @staticmethod - def cafe_from_json(json_eatery: dict, start: datetime.date, end: datetime.date) -> Cafe: - return Cafe( + def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date) -> Eatery: + return Eatery( name=json_eatery["name"], campus_area=json_eatery["campusArea"]["descrshort"], - events=ExternalEateries.cafe_events_from_json( + events=ExternalEateries.eatery_events_from_json( json_operating_hours=json_eatery["operatingHours"], json_dates_closed=json_eatery["datesClosed"], + json_dining_items = json_eatery["diningItems"], start_date=start, - end_date=end + end_date=end, ), latitude=json_eatery["coordinates"]["latitude"], - longitude=json_eatery["coordinates"]["longitude"], - menu=ExternalEateries.cafe_menu_from_json( - json_eatery["diningItems"] - ) + longitude=json_eatery["coordinates"]["longitude"] ) @staticmethod - def cafe_events_from_json( + def eatery_events_from_json( json_operating_hours: list, json_dates_closed: list, + json_dining_items: list, start_date: datetime.date, end_date: datetime.date - ) -> list[CafeEvent]: + ) -> list[Event]: weekday_to_event_templates: dict[int: list[dict]] = {} for json_weekday_event_templates in json_operating_hours: @@ -94,13 +93,15 @@ def cafe_events_from_json( weekday_to_event_templates[weekday] = [] weekday_to_event_templates[weekday].append(event_template) - resolved_events: list[CafeEvent] = [] + resolved_events: list[Event] = [] current = start_date while current <= end_date: if current.weekday() in weekday_to_event_templates: for event_template in weekday_to_event_templates[current.weekday()]: - event = CafeEvent( + event = Event( + description=event_template["descr"], canonical_date=current, + menu=ExternalEateries.eatery_menu_from_json(json_dining_items), start_timestamp=ExternalEateries.timestamp_combined( current, ExternalEateries.time_since_midnight(event_template["start"]) @@ -133,16 +134,16 @@ def time_since_midnight(time_str: str) -> datetime.time: ) @staticmethod - def cafe_menu_from_json(json_dining_items: list) -> CafeMenu: - items = [] - - for json_dining_item in json_dining_items: - items.append(MenuItem( - healthy=json_dining_item["healthy"], - name=json_dining_item["item"] - )) - - return CafeMenu(items=items) + def eatery_menu_from_json(json_dining_items: list) -> Menu: + category_map = {} + for item in json_dining_items: + if item['category'] not in category_map: + category_map[item['category']] = [] + category_map[item['category']].append(MenuItem(healthy=item['healthy'], name = item['item'])) + categories = [] + for category_name in category_map: + categories.append(MenuCategory(category_name, category_map[category_name])) + return Menu(categories=categories) @staticmethod def timestamp_combined(date: datetime.date, time: datetime.time): diff --git a/api/dfg/GoogleSheetsEateries.py b/api/dfg/GoogleSheetsEateries.py index ddb1da0..78a6f42 100644 --- a/api/dfg/GoogleSheetsEateries.py +++ b/api/dfg/GoogleSheetsEateries.py @@ -6,9 +6,10 @@ import pytz -from api.datatype.Cafe import Cafe -from api.datatype.CafeEvent import CafeEvent -from api.datatype.CafeMenu import CafeMenu +from api.datatype.Eatery import Eatery +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem from api.dfg.DfgNode import DfgNode @@ -42,28 +43,27 @@ def __call__(self, *args, **kwargs): json_eateries = json.load(f)["eateries"] for json_eatery in json_eateries: - eateries.append(self.cafe_from_json(json_eatery)) + eateries.append(self.eatery_from_json(json_eatery)) return eateries - def cafe_from_json(self, json_eatery: dict) -> Cafe: - return Cafe( + def eatery_from_json(self, json_eatery: dict) -> Eatery: + return Eatery( name=json_eatery["name"], campus_area=json_eatery["campusArea"]["descrshort"], - events=self.cafe_events_from_google_sheets( + events=self.events_from_google_sheets( table_name=json_eatery["name"], + dining_items=json_eatery["dining_items"] ), latitude=json_eatery["coordinates"]["latitude"], longitude=json_eatery["coordinates"]["longitude"], - menu=GoogleSheetsEateries.cafe_menu_from_json( - json_eatery["diningItems"] - ) ) - def cafe_events_from_google_sheets( + def events_from_google_sheets( self, table_name: str, - ) -> list[CafeEvent]: + dining_items: list + ) -> list[Event]: scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"] creds = None if os.path.exists(self.token_cache_path): @@ -94,13 +94,13 @@ def cafe_events_from_google_sheets( events = [] for row in values: - event = self.parse_row(row) + event = self.parse_row(row, dining_items) if event is not None: events.append(event) return events - def parse_row(self, row: list[str]) -> Optional[CafeEvent]: + def parse_row(self, row: list[str], dining_items: list) -> Optional[Event]: if len(row) != 3: return None @@ -116,10 +116,11 @@ def parse_row(self, row: list[str]) -> Optional[CafeEvent]: if start_time is None or end_time is None: return None - return CafeEvent( + return Event( canonical_date=date, start_timestamp=GoogleSheetsEateries.timestamp_combined(date, start_time), - end_timestamp=GoogleSheetsEateries.timestamp_combined(date, end_time) + end_timestamp=GoogleSheetsEateries.timestamp_combined(date, end_time), + menu=GoogleSheetsEateries.eatery_menu_from_json(dining_items) ) def parse_time(self, time_str: str) -> Optional[datetime.time]: @@ -142,16 +143,16 @@ def parse_time(self, time_str: str) -> Optional[datetime.time]: return None @staticmethod - def cafe_menu_from_json(json_dining_items: list) -> CafeMenu: - items = [] - - for json_dining_item in json_dining_items: - items.append(MenuItem( - healthy=json_dining_item["healthy"], - name=json_dining_item["item"] - )) - - return CafeMenu(items=items) + def eatery_menu_from_json(json_dining_items: list) -> Menu: + category_map = {} + for item in json_dining_items: + if item['category'] not in category_map: + category_map[item['category']] = [] + category_map[item['category']].append(MenuItem(healthy=item['healthy'], name = item['item'])) + categories = [] + for category_name in category_map: + categories.append(MenuCategory(category_name, category_map[category_name])) + return Menu(categories=categories) @staticmethod def timestamp_combined(date: datetime.date, time: datetime.time): diff --git a/api/views.py b/api/views.py index 6c36a65..0f9ac2a 100644 --- a/api/views.py +++ b/api/views.py @@ -7,20 +7,17 @@ from api.dfg.Concat import Concat from api.dfg.CornellDiningNow import CornellDiningNow from api.dfg.DictResponseWrapper import DictResponseWrapper -from api.dfg.EateryGroupByType import EateryGroupByType from api.dfg.EateryToJson import EateryToJson from api.dfg.ExternalEateries import ExternalEateries from api.dfg.GoogleSheetsEateries import GoogleSheetsEateries dataflow_graph = DictResponseWrapper( EateryToJson( - EateryGroupByType( - CalculateWaitTimes( - Concat([ - CornellDiningNow(), - ExternalEateries() - ]) - ) + CalculateWaitTimes( + Concat([ + CornellDiningNow(), + ExternalEateries() + ]) ) ), re_raise_exceptions=True diff --git a/static_sources/external_eateries.json b/static_sources/external_eateries.json index 3c6ee49..7efaa2b 100644 --- a/static_sources/external_eateries.json +++ b/static_sources/external_eateries.json @@ -61,27 +61,33 @@ "diningItems": [ { "item": "Salads", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Burrito Bowl", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Burrito", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Chicken Tenders", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Fries", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Pho", - "healthy": false + "healthy": false, + "category": "General" } ] }, @@ -159,23 +165,28 @@ "diningItems": [ { "item": "Pizza", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Pasta", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Sandwiches", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Sushi to Go", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Soft Drinks", - "healthy": false + "healthy": false, + "category": "General" } ] }, @@ -231,23 +242,28 @@ "diningItems": [ { "item": "Sandwiches", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Soups", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Baked Goods", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Candy", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Soft Drinks", - "healthy": true + "healthy": true, + "category": "General" } ] }, @@ -301,11 +317,13 @@ "diningItems": [ { "item": "Coffee", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Baked Goods", - "healthy": true + "healthy": true, + "category": "General" } ] }, @@ -381,47 +399,58 @@ "diningItems": [ { "item": "French Fries", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Garlic Bread", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Cold Sandwiches", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Hot Sandwiches", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Hot Subs", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Pizza Subs", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Burgers", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Egg Sandwiches", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Hot Dogs", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Wraps", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Salads", - "healthy": false + "healthy": false, + "category": "General" } ] }, @@ -490,51 +519,63 @@ "diningItems": [ { "item": "Whole Grains", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Spices", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Legumes", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Fresh and Frozen Produce", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Nuts", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Dried Fruits", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Tofu", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Eggs", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Milks", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Kombucha on Tap", - "healthy": true + "healthy": true, + "category": "General" }, { "item": "Bottled Drinks", - "healthy": false + "healthy": false, + "category": "General" }, { "item": "Bulk Items", - "healthy": false + "healthy": false, + "category": "General" } ] } From 4860931376485a9700557fb532fcc3377511a994 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Tue, 28 Dec 2021 15:43:57 -0500 Subject: [PATCH 014/305] Fix issue with failed aggregation --- api/datatype/Eatery.py | 5 +- api/datatype/EateryResult.py | 28 +++++++++ api/datatype/WaitTime.py | 17 ++++++ api/datatype/WaitTimesByDay.py | 19 ++++++ api/dfg/CalculateWaitTimes.py | 59 ++++++++++++++----- .../update_transactions_controller.py | 9 ++- ...nsactionhistory_block_end_time_and_more.py | 22 +++++++ ...sactionhistory_bucket_end_time_and_more.py | 22 +++++++ ...lter_transactionhistory_bucket_end_time.py | 18 ++++++ ...lter_transactionhistory_bucket_end_time.py | 18 ++++++ ...nsactionhistory_block_end_time_and_more.py | 22 +++++++ transactions/models.py | 6 +- .../scripts/ingest_log_transactions.py | 2 +- transactions/serializers.py | 12 +++- 14 files changed, 233 insertions(+), 26 deletions(-) create mode 100644 api/datatype/EateryResult.py create mode 100644 api/datatype/WaitTime.py create mode 100644 api/datatype/WaitTimesByDay.py create mode 100644 transactions/migrations/0004_rename_timestamp_transactionhistory_block_end_time_and_more.py create mode 100644 transactions/migrations/0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more.py create mode 100644 transactions/migrations/0006_alter_transactionhistory_bucket_end_time.py create mode 100644 transactions/migrations/0007_alter_transactionhistory_bucket_end_time.py create mode 100644 transactions/migrations/0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more.py diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index d19987a..49b5444 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -1,5 +1,6 @@ from datetime import date -from typing import Optional +from typing import Mapping, Optional +from .WaitTime import WaitTime import pytz @@ -13,7 +14,7 @@ def __init__( campus_area: str, events: list[Event], latitude: float, - longitude: float, + longitude: float ): self.name = name self.campus_area = campus_area diff --git a/api/datatype/EateryResult.py b/api/datatype/EateryResult.py new file mode 100644 index 0000000..a0e9fcf --- /dev/null +++ b/api/datatype/EateryResult.py @@ -0,0 +1,28 @@ +# Eatery type returned to the frontend + +from datetime import date +from typing import Mapping, Optional +from .Eatery import Eatery +from .WaitTimesByDay import WaitTimesByDay + +import pytz + +class EateryResult: + + def __init__( + self, + eatery: Eatery, + wait_times_by_day: list[WaitTimesByDay] + ): + self.eatery = eatery + self.wait_times_by_day = wait_times_by_day + + def to_json( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None + ): + eatery_json = self.eatery.to_json(tzinfo=tzinfo, start=start, end=end) + eatery_json["wait_times_by_day"] = [day_wait_times.to_json() for day_wait_times in self.wait_times_by_day] + return eatery_json diff --git a/api/datatype/WaitTime.py b/api/datatype/WaitTime.py new file mode 100644 index 0000000..63016ca --- /dev/null +++ b/api/datatype/WaitTime.py @@ -0,0 +1,17 @@ + +class WaitTime: + def __init__( + self, + end_timestamp: int, + wait: float + ): + self.end_timestamp = end_timestamp + self.wait = wait + + def to_json(self): + return { + "block_end_timestamp": self.end_timestamp, + "block_duration": 5 * 60 * 60, + "wait": self.wait + } + diff --git a/api/datatype/WaitTimesByDay.py b/api/datatype/WaitTimesByDay.py new file mode 100644 index 0000000..c62eed2 --- /dev/null +++ b/api/datatype/WaitTimesByDay.py @@ -0,0 +1,19 @@ + + +from .WaitTime import WaitTime + +class WaitTimesByDay: + def __init__( + self, + canonical_date: str, + wait_times: list[WaitTime] + ): + self.canonical_date = canonical_date + self.wait_times = wait_times + + def to_json(self): + return { + "canonical_date": self.canonical_date, + "wait_times": [wait_time.to_json() for wait_time in self.wait_times] + } + diff --git a/api/dfg/CalculateWaitTimes.py b/api/dfg/CalculateWaitTimes.py index d955b40..4bbb7cf 100644 --- a/api/dfg/CalculateWaitTimes.py +++ b/api/dfg/CalculateWaitTimes.py @@ -1,30 +1,61 @@ from api.dfg.DfgNode import DfgNode from transactions.models import TransactionHistory from transactions.serializers import TransactionHistorySerializer +from ..datatype.EateryResult import EateryResult +from ..datatype.WaitTime import WaitTime +from ..datatype.WaitTimesByDay import WaitTimesByDay -import datetime +from django.db.models import Avg +from datetime import datetime, timedelta, tzinfo +import pytz class CalculateWaitTimes(DfgNode): def __init__(self, child: DfgNode): self.child = child - def __call__(self, *args, **kwargs): - eateries_with_wait_times = [] + def __call__(self, *args, **kwargs) -> list[EateryResult]: + eatery_results = [] + wait_times = {} # [date]: {[eateryname]: list[WaitTime]} past_days = [] - # kwargs.get("start") - for i in range(1, 13): - past_day = datetime.date.today() - datetime.timedelta(days = 7 * i) - past_days.append(past_day) - - transaction_data = TransactionHistory.objects.filter(canonical_date__in=past_days) - # print(len(transaction_data)) - # print('--------------------') - # print(transaction_data) + date = kwargs.get("start") + while date <= kwargs.get("end"): + # We only calculate the wait times for this first day + eatery_waits_on_date = {} + for i in range(1, 13): + # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the transaction_count + past_day = date - timedelta(days = 7 * i) + past_days.append(past_day) + transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days).values("name", "block_end_time").annotate(transaction_avg=Avg("transaction_count")) + for unit in transaction_avg_counts: + transaction_history = TransactionHistorySerializer(unit) + eatery_name = transaction_history.data['name'] + block_end_time = datetime.strptime(transaction_history.data['block_end_time'], '%H:%M:%S').time() + block_end_timestamp = CalculateWaitTimes.timestamp_combined(date, block_end_time) + transaction_average = transaction_history.data["transaction_avg"] + if eatery_name not in eatery_waits_on_date: + eatery_waits_on_date[eatery_name] = [] + eatery_waits_on_date[eatery_name].append(WaitTime(block_end_timestamp, transaction_average)) + wait_times[date] = eatery_waits_on_date + date += timedelta(days=1) for eatery in self.child(*args, **kwargs): - eateries_with_wait_times.append(eatery) + eatery_wait_times_by_day = [] + for date in wait_times: + if eatery.name in wait_times[date]: + eatery_wait_times_by_day.append(WaitTimesByDay(date, wait_times[date][eatery.name])) + eatery_results.append(EateryResult(eatery, eatery_wait_times_by_day)) - return eateries_with_wait_times + return eatery_results + + @staticmethod + def timestamp_combined(date: datetime.date, time: datetime.time): + """ + Returns the Unix (UTC) timestamp of the combined (date, time) in the + New York timezone. + """ + + tz = pytz.timezone('America/New_York') + return int(tz.localize(datetime.combine(date, time)).timestamp()) def children(self): return [self.child] diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index a9a2e26..c4251c1 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -1,8 +1,9 @@ from django.contrib.auth.models import User from django.core.paginator import Paginator from django.db.models import Q -from datetime import datetime, timedelta +from datetime import datetime, timedelta, tzinfo from random import randrange +import pytz from transactions.models import TransactionHistory class UpdateTransactionsController: @@ -95,8 +96,10 @@ def process(self): "result": None, "error": "Invalid date" } - recent_datetime = datetime.strptime(self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p') + tz = pytz.timezone('America/New_York') + recent_datetime = tz.localize(datetime.strptime(self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p')) canonical_date = recent_datetime.date() + block_end_time = recent_datetime.time() if recent_datetime.hour < 4: # between 12am and 4am associate this with the previous day canonical_date = canonical_date - timedelta(days=1) @@ -109,7 +112,7 @@ def process(self): else: num_inserted += 1 try: - TransactionHistory.objects.create(name = name, canonical_date = canonical_date, timestamp=recent_datetime, transaction_count=place["CROWD_COUNT"]) + TransactionHistory.objects.create(name = name, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) except: num_inserted -= 1 return { diff --git a/transactions/migrations/0004_rename_timestamp_transactionhistory_block_end_time_and_more.py b/transactions/migrations/0004_rename_timestamp_transactionhistory_block_end_time_and_more.py new file mode 100644 index 0000000..340380c --- /dev/null +++ b/transactions/migrations/0004_rename_timestamp_transactionhistory_block_end_time_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.0 on 2021-12-28 19:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0003_remove_transactionhistory_transaction_name_204126_idx_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='transactionhistory', + old_name='timestamp', + new_name='block_end_time', + ), + migrations.AlterUniqueTogether( + name='transactionhistory', + unique_together={('name', 'block_end_time', 'canonical_date')}, + ), + ] diff --git a/transactions/migrations/0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more.py b/transactions/migrations/0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more.py new file mode 100644 index 0000000..11ad3cc --- /dev/null +++ b/transactions/migrations/0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.0 on 2021-12-28 20:18 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0004_rename_timestamp_transactionhistory_block_end_time_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='transactionhistory', + old_name='block_end_time', + new_name='bucket_end_time', + ), + migrations.AlterUniqueTogether( + name='transactionhistory', + unique_together={('name', 'bucket_end_time', 'canonical_date')}, + ), + ] diff --git a/transactions/migrations/0006_alter_transactionhistory_bucket_end_time.py b/transactions/migrations/0006_alter_transactionhistory_bucket_end_time.py new file mode 100644 index 0000000..d8dfe3e --- /dev/null +++ b/transactions/migrations/0006_alter_transactionhistory_bucket_end_time.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2021-12-28 20:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='transactionhistory', + name='bucket_end_time', + field=models.IntegerField(), + ), + ] diff --git a/transactions/migrations/0007_alter_transactionhistory_bucket_end_time.py b/transactions/migrations/0007_alter_transactionhistory_bucket_end_time.py new file mode 100644 index 0000000..9d28078 --- /dev/null +++ b/transactions/migrations/0007_alter_transactionhistory_bucket_end_time.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2021-12-28 20:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0006_alter_transactionhistory_bucket_end_time'), + ] + + operations = [ + migrations.AlterField( + model_name='transactionhistory', + name='bucket_end_time', + field=models.TimeField(), + ), + ] diff --git a/transactions/migrations/0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more.py b/transactions/migrations/0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more.py new file mode 100644 index 0000000..fcba274 --- /dev/null +++ b/transactions/migrations/0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 4.0 on 2021-12-28 20:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0007_alter_transactionhistory_bucket_end_time'), + ] + + operations = [ + migrations.RenameField( + model_name='transactionhistory', + old_name='bucket_end_time', + new_name='block_end_time', + ), + migrations.AlterUniqueTogether( + name='transactionhistory', + unique_together={('name', 'block_end_time', 'canonical_date')}, + ), + ] diff --git a/transactions/models.py b/transactions/models.py index c141803..5811d26 100644 --- a/transactions/models.py +++ b/transactions/models.py @@ -2,12 +2,12 @@ # Create your models here. -# [transaction_count] transactions at [name] in time range [timestamp - 5 minutes, timestamp] on [canonical_date] +# [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] class TransactionHistory(models.Model): class Meta: - unique_together = ('name', 'timestamp', 'canonical_date') + unique_together = ('name', 'block_end_time', 'canonical_date') indexes = [models.Index(fields = ['canonical_date'])] name = models.CharField(max_length=100) canonical_date = models.DateField() - timestamp = models.TimeField() + block_end_time = models.TimeField() transaction_count = models.IntegerField() \ No newline at end of file diff --git a/transactions/scripts/ingest_log_transactions.py b/transactions/scripts/ingest_log_transactions.py index ca4de15..5f4813e 100644 --- a/transactions/scripts/ingest_log_transactions.py +++ b/transactions/scripts/ingest_log_transactions.py @@ -19,7 +19,7 @@ def ingest(request): timestamp = time.strptime(data['TIMESTAMP'], '%Y-%m-%d %I:%M:%S %p') if counter % 100 == 1: print(timestamp) - if timestamp.tm_year == 2021: + if timestamp.tm_year == 2021 and timestamp.tm_mon > 7: counter += 1 res = UpdateTransactionsController(data).process() if res["success"]: diff --git a/transactions/serializers.py b/transactions/serializers.py index 91eff4a..2ebaa10 100644 --- a/transactions/serializers.py +++ b/transactions/serializers.py @@ -3,17 +3,23 @@ from rest_framework import serializers from .models import TransactionHistory - class TransactionHistorySerializer(serializers.ModelSerializer): + transaction_avg = serializers.SerializerMethodField("get_transaction_avg") + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + def get_transaction_avg(self, obj): + try: + return obj['transaction_avg'] + except: + return None class Meta: model = TransactionHistory fields = ( "name", "canonical_date", - "timestamp", - "transaction_count" + "block_end_time", + "transaction_avg" ) read_only_fields = fields \ No newline at end of file From 6fb7c09d90eefe5c4dfb8c60b58efa51074b363a Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 30 Dec 2021 01:16:06 -0500 Subject: [PATCH 015/305] Wait times minute estimation, mac's terrace and mattins vals --- api/datatype/WaitTime.py | 16 ++- api/dfg/CalculateWaitTimes.py | 105 +++++++++++++++--- api/dfg/CornellDiningNow.py | 1 - static_sources/external_eateries.json | 2 +- .../update_transactions_controller.py | 2 +- 5 files changed, 102 insertions(+), 24 deletions(-) diff --git a/api/datatype/WaitTime.py b/api/datatype/WaitTime.py index 63016ca..cff3553 100644 --- a/api/datatype/WaitTime.py +++ b/api/datatype/WaitTime.py @@ -1,17 +1,25 @@ - class WaitTime: def __init__( self, end_timestamp: int, - wait: float + wait_time_low: float, + wait_time_expected: float, + wait_time_high: float, + relative_density: float ): self.end_timestamp = end_timestamp - self.wait = wait + self.wait_time_low = wait_time_low + self.wait_time_expected = wait_time_expected + self.wait_time_high = wait_time_high + self.relative_density = relative_density def to_json(self): return { "block_end_timestamp": self.end_timestamp, "block_duration": 5 * 60 * 60, - "wait": self.wait + "wait_time_low": self.wait_time_low, + "wait_time_expected": self.wait_time_expected, + "wait_time_high": self.wait_time_high, + "relative_density": self.relative_density, } diff --git a/api/dfg/CalculateWaitTimes.py b/api/dfg/CalculateWaitTimes.py index 4bbb7cf..ff3c945 100644 --- a/api/dfg/CalculateWaitTimes.py +++ b/api/dfg/CalculateWaitTimes.py @@ -1,3 +1,5 @@ +from typing import Mapping +from api.datatype.Eatery import Eatery from api.dfg.DfgNode import DfgNode from transactions.models import TransactionHistory from transactions.serializers import TransactionHistorySerializer @@ -6,9 +8,8 @@ from ..datatype.WaitTimesByDay import WaitTimesByDay from django.db.models import Avg -from datetime import datetime, timedelta, tzinfo +from datetime import date, datetime, timedelta, tzinfo import pytz - class CalculateWaitTimes(DfgNode): def __init__(self, child: DfgNode): @@ -16,37 +17,107 @@ def __init__(self, child: DfgNode): def __call__(self, *args, **kwargs) -> list[EateryResult]: eatery_results = [] - wait_times = {} # [date]: {[eateryname]: list[WaitTime]} + transactions_by_date = {} # [date]: {[eateryname]: list[TransactionHistory]} past_days = [] date = kwargs.get("start") while date <= kwargs.get("end"): # We only calculate the wait times for this first day - eatery_waits_on_date = {} + transactions_on_date = {} for i in range(1, 13): # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the transaction_count past_day = date - timedelta(days = 7 * i) past_days.append(past_day) - transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days).values("name", "block_end_time").annotate(transaction_avg=Avg("transaction_count")) + transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ + .values("name", "block_end_time") \ + .annotate(transaction_avg=Avg("transaction_count")) for unit in transaction_avg_counts: transaction_history = TransactionHistorySerializer(unit) eatery_name = transaction_history.data['name'] - block_end_time = datetime.strptime(transaction_history.data['block_end_time'], '%H:%M:%S').time() - block_end_timestamp = CalculateWaitTimes.timestamp_combined(date, block_end_time) - transaction_average = transaction_history.data["transaction_avg"] - if eatery_name not in eatery_waits_on_date: - eatery_waits_on_date[eatery_name] = [] - eatery_waits_on_date[eatery_name].append(WaitTime(block_end_timestamp, transaction_average)) - wait_times[date] = eatery_waits_on_date + if eatery_name not in transactions_on_date: + transactions_on_date[eatery_name] = [] + transactions_on_date[eatery_name].append(transaction_history) + transactions_by_date[date] = transactions_on_date date += timedelta(days=1) for eatery in self.child(*args, **kwargs): - eatery_wait_times_by_day = [] - for date in wait_times: - if eatery.name in wait_times[date]: - eatery_wait_times_by_day.append(WaitTimesByDay(date, wait_times[date][eatery.name])) - eatery_results.append(EateryResult(eatery, eatery_wait_times_by_day)) + eatery_results.append(CalculateWaitTimes.generate_eatery_result(eatery, transactions_by_date)) return eatery_results + # Expected amount of time (in minutes) for the length of the line to decrease by 1 person + # Returns [lower, expected, upper] + @staticmethod + def LINE_DECREASE_BY_ONE_TIME(eatery_name: str) -> float: + # TODO: Move these hardcoded names into a string file + if eatery_name == "Mac's Café": + return [0.4, 0.45, 0.5] + elif eatery_name == "Mattin's Café": + return [0.15, 0.25, 0.35] + elif eatery_name == "Terrace Restaurant": + return [0.25, 0.45, 0.6] + else: + return [0.3, 0.35, 0.4] + + # Expected amount of time (in minutes) for a person to get food, assuming an empty eatery, not including the amount of time to check out + # Returns [lower, expected, upper] + @staticmethod + def BASE_TIME_TO_GET_FOOD(eatery_name: str) -> float: + if eatery_name == "Mac's Café": + return [4, 5, 6] + elif eatery_name == "Mattin's Café": + return [2.5, 3.5, 4.5] + elif eatery_name == "Terrace Restaurant": + return [3, 5, 7] + else: + return [3, 4, 5] + + @staticmethod + def generate_eatery_wait_times_by_day( + eatery: Eatery, + date: date, + transactions: list[TransactionHistorySerializer] + ) -> WaitTimesByDay: + wait_times = [] + customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + for index in reversed(range(0, len(transactions))): + base_times = CalculateWaitTimes.BASE_TIME_TO_GET_FOOD(eatery.name) + line_decrease_times = CalculateWaitTimes.LINE_DECREASE_BY_ONE_TIME(eatery.name) + # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago + how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] + prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // 5) + if prev_bucket_guest_arrival > 9: + print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") + else: + customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index].data["transaction_avg"] + num_customers = customers_waiting_in_line.pop(0) + wait_time_low = base_times[0] + line_decrease_times[0] * num_customers + wait_time_expected = base_times[1] + line_decrease_times[1] * num_customers + wait_time_high = base_times[2] + line_decrease_times[2] * num_customers + relative_density = num_customers + + customers_waiting_in_line.append(0.0) + block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() + block_end_timestamp = CalculateWaitTimes.timestamp_combined(date, block_end_time) + if any([block_end_timestamp in event for event in eatery.events()]): + wait_times.insert(0, WaitTime( + end_timestamp=block_end_timestamp, + wait_time_low = wait_time_low, + wait_time_expected= wait_time_expected, + wait_time_high = wait_time_high, + relative_density= relative_density)) + + return WaitTimesByDay(date, wait_times) + + @staticmethod + def generate_eatery_result( + eatery: Eatery, + transactions_by_date: Mapping[date, Mapping[str, list[TransactionHistorySerializer]]] + ) -> EateryResult: + eatery_wait_times_by_day = [] + for date in transactions_by_date: + if eatery.name in transactions_by_date[date]: + eatery_wait_times_by_day.append(CalculateWaitTimes.generate_eatery_wait_times_by_day(eatery, date, transactions_by_date[date][eatery.name])) + return EateryResult(eatery, eatery_wait_times_by_day) + @staticmethod def timestamp_combined(date: datetime.date, time: datetime.time): """ diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index 41fdd4e..fc8494b 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -48,7 +48,6 @@ def parse_eatery(json_eatery: dict) -> Eatery: json_dining_items = json_eatery["diningItems"], is_cafe = is_cafe ), - ) @staticmethod diff --git a/static_sources/external_eateries.json b/static_sources/external_eateries.json index 7efaa2b..5c21207 100644 --- a/static_sources/external_eateries.json +++ b/static_sources/external_eateries.json @@ -94,7 +94,7 @@ { "slug": "Macs", "external": true, - "name": "Mac’s Café", + "name": "Mac's Café", "nameshort": "Mac's", "about": "", "cornellDining": false, diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index c4251c1..bc8f57d 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -75,7 +75,7 @@ def eatery_name(vendor_eatery_name): elif vendor_eatery_name == "manncafe": return "Mann Café" elif vendor_eatery_name == "statlermacs": - return "Mac's Café" # NOTE: Mac's apostrophe character is different from normal. Using normal apostrophe here + return "Mac's Café" else: # TODO: Add a slack notif / flag that a wait time location was not recognized return "" From 36541236cc66f37db0efac7beb6e6f16de6ba082 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 30 Dec 2021 02:22:37 -0500 Subject: [PATCH 016/305] Cache update and reenable --- api/dfg/EateryToJson.py | 4 +-- api/dfg/InMemoryCache.py | 71 +++++++++++++++++++++++++++++++--------- api/views.py | 13 +++++--- 3 files changed, 65 insertions(+), 23 deletions(-) diff --git a/api/dfg/EateryToJson.py b/api/dfg/EateryToJson.py index 9ee9a7b..8e0515c 100644 --- a/api/dfg/EateryToJson.py +++ b/api/dfg/EateryToJson.py @@ -1,6 +1,6 @@ from typing import Union -from api.datatype.Eatery import Eatery +from api.datatype.EateryResult import EateryResult from api.dfg.DfgNode import DfgNode @@ -14,7 +14,7 @@ def __call__(self, *args, **kwargs): return EateryToJson.to_json(result, *args, **kwargs) @staticmethod - def to_json(obj: Union[list, dict, Eatery], *args, **kwargs): + def to_json(obj: Union[list, dict, EateryResult], *args, **kwargs): if isinstance(obj, list): return [ EateryToJson.to_json(elem, *args, **kwargs) diff --git a/api/dfg/InMemoryCache.py b/api/dfg/InMemoryCache.py index 60846a7..9a9eae2 100644 --- a/api/dfg/InMemoryCache.py +++ b/api/dfg/InMemoryCache.py @@ -1,37 +1,76 @@ import time from api.dfg.DfgNode import DfgNode +from typing import Optional +from api.dfg.EateryToJson import EateryToJson + + +class DataSnapshot(): + def __init__(self, args, kwargs, data, time): + self.data = data + self.recorded_time = time + self.args = args + self.kwargs = kwargs + + def is_usable_snapshot(self, oldest_possible_time, args, kwargs): + return args == self.args and kwargs == self.kwargs and self.recorded_time >= oldest_possible_time + + def get_data(self): + return self.data + + def to_json(self): + return EateryToJson.to_json(self.data, *self.args, **self.kwargs) + + def get_recorded_time(self): + return self.recorded_time class InMemoryCache(DfgNode): - def __init__(self, child, expiration: float = 60): + def __init__(self, child, expiration: float = 60, max_size: int = 5): self.child = child self.expiration = expiration - self.last_refresh_time = None - self.last_refresh_data = None + self.max_size = max_size + self.snapshots: list[DataSnapshot] = [] + def current_time(self): return time.time() - def is_expired(self): - return ( - self.last_refresh_data is None - or self.last_refresh_time is None - or (self.current_time() - self.last_refresh_time) > self.expiration - ) + def fifo_index(self): + if len(self.snapshots) == 0: + return None + oldest_snapshot_time = self.snapshots[0].get_recorded_time() + oldest_snapshot_index = 0 + for i in range(1, len(self.snapshots)): + if self.snapshots[i].get_recorded_time() < oldest_snapshot_time: + oldest_snapshot_time = self.snapshots[i].get_recorded_time() + oldest_snapshot_index = i + return oldest_snapshot_index def __call__(self, *args, **kwargs): - if self.is_expired(): - self.last_refresh_data = self.child() - self.last_refresh_time = self.current_time() - print(f"{self}: Fetching data") + for snapshot in self.snapshots: + if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): + print(f"{self}: Returning from cache") + return snapshot.get_data() + print(f"{self}: Fetching data") + new_snapshot = DataSnapshot(args, kwargs, self.child(*args, **kwargs), self.current_time()) + if len(self.snapshots) < self.max_size: + self.snapshots.append(new_snapshot) else: - print(f"{self}: Returning from cache") - - return self.last_refresh_data + index_to_replace = self.fifo_index() + self.snapshots[index_to_replace] = new_snapshot + return new_snapshot.get_data() def children(self): return [self.child] + def to_json(self, *args, **kwargs): + for snapshot in self.snapshots: + if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): + print(f"{self}: Returning from cache") + return snapshot.to_json() + + return EateryToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) + diff --git a/api/views.py b/api/views.py index 0f9ac2a..91577b2 100644 --- a/api/views.py +++ b/api/views.py @@ -10,14 +10,17 @@ from api.dfg.EateryToJson import EateryToJson from api.dfg.ExternalEateries import ExternalEateries from api.dfg.GoogleSheetsEateries import GoogleSheetsEateries +from api.dfg.InMemoryCache import InMemoryCache dataflow_graph = DictResponseWrapper( EateryToJson( - CalculateWaitTimes( - Concat([ - CornellDiningNow(), - ExternalEateries() - ]) + InMemoryCache( + CalculateWaitTimes( + Concat([ + CornellDiningNow(), + ExternalEateries() + ]) + ) ) ), re_raise_exceptions=True From 9e59d5ea92b0e5663553e29f81a167cc928375fb Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 30 Dec 2021 02:24:06 -0500 Subject: [PATCH 017/305] clean-up --- api/dfg/InMemoryCache.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/dfg/InMemoryCache.py b/api/dfg/InMemoryCache.py index 9a9eae2..34bef6e 100644 --- a/api/dfg/InMemoryCache.py +++ b/api/dfg/InMemoryCache.py @@ -69,8 +69,6 @@ def children(self): def to_json(self, *args, **kwargs): for snapshot in self.snapshots: if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): - print(f"{self}: Returning from cache") return snapshot.to_json() - return EateryToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) From 9075d27078ab39c5438b7ee225401b30c0240cec Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 30 Dec 2021 23:52:08 -0500 Subject: [PATCH 018/305] Timestamp changes --- api/datatype/WaitTime.py | 14 +++++------- api/dfg/CalculateWaitTimes.py | 38 ++++++++++++++++----------------- api/dfg/InMemoryCache.py | 2 +- eatery_blue_backend/settings.py | 2 +- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/api/datatype/WaitTime.py b/api/datatype/WaitTime.py index cff3553..5bf4d5b 100644 --- a/api/datatype/WaitTime.py +++ b/api/datatype/WaitTime.py @@ -1,25 +1,21 @@ class WaitTime: def __init__( self, - end_timestamp: int, + timestamp: int, wait_time_low: float, wait_time_expected: float, - wait_time_high: float, - relative_density: float + wait_time_high: float ): - self.end_timestamp = end_timestamp + self.timestamp = timestamp self.wait_time_low = wait_time_low self.wait_time_expected = wait_time_expected self.wait_time_high = wait_time_high - self.relative_density = relative_density def to_json(self): return { - "block_end_timestamp": self.end_timestamp, - "block_duration": 5 * 60 * 60, + "timestamp": self.timestamp, "wait_time_low": self.wait_time_low, "wait_time_expected": self.wait_time_expected, - "wait_time_high": self.wait_time_high, - "relative_density": self.relative_density, + "wait_time_high": self.wait_time_high } diff --git a/api/dfg/CalculateWaitTimes.py b/api/dfg/CalculateWaitTimes.py index ff3c945..ec1efd0 100644 --- a/api/dfg/CalculateWaitTimes.py +++ b/api/dfg/CalculateWaitTimes.py @@ -43,32 +43,32 @@ def __call__(self, *args, **kwargs) -> list[EateryResult]: return eatery_results - # Expected amount of time (in minutes) for the length of the line to decrease by 1 person + # Expected amount of time (in seconds) for the length of the line to decrease by 1 person # Returns [lower, expected, upper] @staticmethod def LINE_DECREASE_BY_ONE_TIME(eatery_name: str) -> float: # TODO: Move these hardcoded names into a string file if eatery_name == "Mac's Café": - return [0.4, 0.45, 0.5] + return [24, 27, 30] elif eatery_name == "Mattin's Café": - return [0.15, 0.25, 0.35] + return [9, 15, 21] elif eatery_name == "Terrace Restaurant": - return [0.25, 0.45, 0.6] + return [15, 27, 36] else: - return [0.3, 0.35, 0.4] + return [18, 21, 24] - # Expected amount of time (in minutes) for a person to get food, assuming an empty eatery, not including the amount of time to check out + # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the amount of time to check out # Returns [lower, expected, upper] @staticmethod def BASE_TIME_TO_GET_FOOD(eatery_name: str) -> float: if eatery_name == "Mac's Café": - return [4, 5, 6] + return [240, 300, 360] elif eatery_name == "Mattin's Café": - return [2.5, 3.5, 4.5] + return [150, 210, 270] elif eatery_name == "Terrace Restaurant": - return [3, 5, 7] + return [180, 300, 420] else: - return [3, 4, 5] + return [180, 240, 300] @staticmethod def generate_eatery_wait_times_by_day( @@ -83,27 +83,25 @@ def generate_eatery_wait_times_by_day( line_decrease_times = CalculateWaitTimes.LINE_DECREASE_BY_ONE_TIME(eatery.name) # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] - prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // 5) + prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) if prev_bucket_guest_arrival > 9: print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") else: customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index].data["transaction_avg"] num_customers = customers_waiting_in_line.pop(0) - wait_time_low = base_times[0] + line_decrease_times[0] * num_customers - wait_time_expected = base_times[1] + line_decrease_times[1] * num_customers - wait_time_high = base_times[2] + line_decrease_times[2] * num_customers - relative_density = num_customers + wait_time_low = int(base_times[0] + line_decrease_times[0] * num_customers) + wait_time_expected = int(base_times[1] + line_decrease_times[1] * num_customers) + wait_time_high = int(base_times[2] + line_decrease_times[2] * num_customers) customers_waiting_in_line.append(0.0) block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() - block_end_timestamp = CalculateWaitTimes.timestamp_combined(date, block_end_time) - if any([block_end_timestamp in event for event in eatery.events()]): + timestamp = int(CalculateWaitTimes.timestamp_combined(date, block_end_time) - 5 * 60 / 2) + if any([timestamp in event for event in eatery.events()]): wait_times.insert(0, WaitTime( - end_timestamp=block_end_timestamp, + timestamp=timestamp, wait_time_low = wait_time_low, wait_time_expected= wait_time_expected, - wait_time_high = wait_time_high, - relative_density= relative_density)) + wait_time_high = wait_time_high)) return WaitTimesByDay(date, wait_times) diff --git a/api/dfg/InMemoryCache.py b/api/dfg/InMemoryCache.py index 34bef6e..d106d59 100644 --- a/api/dfg/InMemoryCache.py +++ b/api/dfg/InMemoryCache.py @@ -27,7 +27,7 @@ def get_recorded_time(self): class InMemoryCache(DfgNode): - def __init__(self, child, expiration: float = 60, max_size: int = 5): + def __init__(self, child, expiration: float = 3600, max_size: int = 5): self.child = child self.expiration = expiration self.max_size = max_size diff --git a/eatery_blue_backend/settings.py b/eatery_blue_backend/settings.py index 68322a3..b148ba9 100644 --- a/eatery_blue_backend/settings.py +++ b/eatery_blue_backend/settings.py @@ -25,7 +25,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ["d706-2601-187-8400-2076-1158-3d93-7b45-1456.ngrok.io"] # Application definition From 4329271e2f4260b346d49810183a2802a3f8d435 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Fri, 31 Dec 2021 01:03:15 -0500 Subject: [PATCH 019/305] add payment_methods, location, online_order_url, updated ingest_log_transactions to post req --- api/datatype/Eatery.py | 15 +++++++++++-- api/dfg/CornellDiningNow.py | 16 ++++++++++++++ api/dfg/ExternalEateries.py | 22 +++++++++++++++++-- eatery_blue_backend/settings.py | 2 +- static_sources/external_eateries.json | 13 ++++++++++- .../scripts/ingest_log_transactions.py | 5 ++++- 6 files changed, 66 insertions(+), 7 deletions(-) diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index 49b5444..7058ec9 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -14,13 +14,19 @@ def __init__( campus_area: str, events: list[Event], latitude: float, - longitude: float + longitude: float, + payment_methods: list[str], + location: str, + online_order_url: Optional[str] = None ): self.name = name self.campus_area = campus_area self.latitude = latitude self.longitude = longitude self.known_events = events + self.payment_methods = payment_methods + self.location = location + self.online_order_url = online_order_url def events( self, @@ -36,11 +42,16 @@ def to_json( start: Optional[date] = None, end: Optional[date] = None ): - return { + eatery = { "campus_area": self.campus_area, "events": [event.to_json() for event in self.events(tzinfo, start, end)], "latitude": self.latitude, "longitude": self.longitude, "name": self.name, + "payment_methods": [payment_method for payment_method in self.payment_methods], + "location": self.location, } + if self.online_order_url is not None: + eatery["online_order_url"] = self.online_order_url + return eatery diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index fc8494b..54b2700 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -48,8 +48,24 @@ def parse_eatery(json_eatery: dict) -> Eatery: json_dining_items = json_eatery["diningItems"], is_cafe = is_cafe ), + payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), + online_order_url=json_eatery["onlineOrderUrl"] if json_eatery["onlineOrdering"] else None ) + @staticmethod + def generate_payment_methods(json_paymethods: list): + payment_methods = [] + takes_cash = True + takes_brbs = any([method["descrshort"] == "Meal Plan - Debit" for method in json_paymethods]) + takes_swipes = any([method["descrshort"] == "Meal Plan - Swipe" for method in json_paymethods]) + if takes_cash: + payment_methods.append("cash") + if takes_brbs: + payment_methods.append("brbs") + if takes_swipes: + payment_methods.append("swipes") + return payment_methods + @staticmethod def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: json_operating_hours = sorted( diff --git a/api/dfg/ExternalEateries.py b/api/dfg/ExternalEateries.py index 9220645..0207d4a 100644 --- a/api/dfg/ExternalEateries.py +++ b/api/dfg/ExternalEateries.py @@ -15,7 +15,7 @@ class ExternalEateries(DfgNode): - + # TODO: Make parsing of ExternalEateries the same as parsing of normal eateries, except from file, and then read external data on top of this EXTERNAL_EATERIES_PATH = "static_sources/external_eateries.json" # based on date.weekday() @@ -60,9 +60,27 @@ def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date end_date=end, ), latitude=json_eatery["coordinates"]["latitude"], - longitude=json_eatery["coordinates"]["longitude"] + longitude=json_eatery["coordinates"]["longitude"], + payment_methods=ExternalEateries.generate_payment_methods(json_eatery["payMethods"]), + online_order_url=json_eatery["onlineOrderUrl"] if json_eatery["onlineOrdering"] else None ) + + @staticmethod + def generate_payment_methods(json_paymethods: list): + payment_methods = [] + takes_cash = True + takes_brbs = any([method["descrshort"] == "Meal Plan - Debit" for method in json_paymethods]) + takes_swipes = any([method["descrshort"] == "Meal Plan - Swipe" for method in json_paymethods]) + if takes_cash: + payment_methods.append("cash") + if takes_brbs: + payment_methods.append("brbs") + if takes_swipes: + payment_methods.append("swipes") + return payment_methods + + @staticmethod def eatery_events_from_json( json_operating_hours: list, diff --git a/eatery_blue_backend/settings.py b/eatery_blue_backend/settings.py index b148ba9..cb312ce 100644 --- a/eatery_blue_backend/settings.py +++ b/eatery_blue_backend/settings.py @@ -25,7 +25,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ["d706-2601-187-8400-2076-1158-3d93-7b45-1456.ngrok.io"] +ALLOWED_HOSTS = ["d706-2601-187-8400-2076-1158-3d93-7b45-1456.ngrok.io", "127.0.0.1"] # Application definition diff --git a/static_sources/external_eateries.json b/static_sources/external_eateries.json index 5c21207..889ba80 100644 --- a/static_sources/external_eateries.json +++ b/static_sources/external_eateries.json @@ -6,7 +6,6 @@ "name": "Terrace Restaurant", "nameshort": "Terrace", "about": "", - "cornellDining": false, "contactPhone": "1-800-541-2501", "coordinates": { "latitude": 42.446267, @@ -23,6 +22,8 @@ "descrshort": "cafe" } ], + "onlineOrdering": false, + "onlineOrderUrl": null, "operatingHours": [ { "weekday": "monday-friday", @@ -114,6 +115,8 @@ "descrshort": "cafe" } ], + "onlineOrdering": false, + "onlineOrderUrl": null, "operatingHours": [ { "weekday": "monday-friday", @@ -213,6 +216,8 @@ "descrshort": "cafe" } ], + "onlineOrdering": false, + "onlineOrderUrl": null, "operatingHours": [ { "weekday": "monday-friday", @@ -290,6 +295,8 @@ "descrshort": "cafe" } ], + "onlineOrdering": false, + "onlineOrderUrl": null, "operatingHours": [ { "weekday": "monday-friday", @@ -350,6 +357,8 @@ "descrshort": "cafe" } ], + "onlineOrdering": false, + "onlineOrderUrl": null, "operatingHours": [ { "weekday": "monday-friday", @@ -477,6 +486,8 @@ "descrshort": "cafe" } ], + "onlineOrdering": false, + "onlineOrderUrl": null, "operatingHours": [ { "weekday": "wednesday-thursday", diff --git a/transactions/scripts/ingest_log_transactions.py b/transactions/scripts/ingest_log_transactions.py index 5f4813e..1eac8a2 100644 --- a/transactions/scripts/ingest_log_transactions.py +++ b/transactions/scripts/ingest_log_transactions.py @@ -1,13 +1,16 @@ # Transaction Histories used to be stored in a giant log file. Ingest that log file into the db -import requests import json import time from django.http import JsonResponse +from rest_framework.decorators import api_view +from django.views.decorators.csrf import csrf_exempt from transactions.controllers.update_transactions_controller import UpdateTransactionsController from ..models import TransactionHistory +@api_view(['POST']) +@csrf_exempt def ingest(request): num_deleted = TransactionHistory.objects.all().delete()[0] counter = 0 From 846680b2c8b3648f8c9be38b680f89d8b3453692 Mon Sep 17 00:00:00 2001 From: William Ma Date: Fri, 31 Dec 2021 01:15:59 -0500 Subject: [PATCH 020/305] Include location parameter when constructing eatery --- api/datatype/Eatery.py | 2 +- api/dfg/CornellDiningNow.py | 1 + api/dfg/ExternalEateries.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index 7058ec9..1cc743a 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -16,7 +16,7 @@ def __init__( latitude: float, longitude: float, payment_methods: list[str], - location: str, + location: Optional[str], online_order_url: Optional[str] = None ): self.name = name diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index 54b2700..91e0cb0 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -49,6 +49,7 @@ def parse_eatery(json_eatery: dict) -> Eatery: is_cafe = is_cafe ), payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), + location=None, online_order_url=json_eatery["onlineOrderUrl"] if json_eatery["onlineOrdering"] else None ) diff --git a/api/dfg/ExternalEateries.py b/api/dfg/ExternalEateries.py index 0207d4a..af0a171 100644 --- a/api/dfg/ExternalEateries.py +++ b/api/dfg/ExternalEateries.py @@ -62,6 +62,7 @@ def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date latitude=json_eatery["coordinates"]["latitude"], longitude=json_eatery["coordinates"]["longitude"], payment_methods=ExternalEateries.generate_payment_methods(json_eatery["payMethods"]), + location=None, online_order_url=json_eatery["onlineOrderUrl"] if json_eatery["onlineOrdering"] else None ) From 1ac51be0109b9413a9716806138c7ad2a38277c3 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 3 Jan 2022 10:47:35 -0500 Subject: [PATCH 021/305] Backend dfg refactor --- api/datatype/__init__.py | 0 api/dfg/Concat.py | 21 --- api/dfg/DfgNode.py | 3 - api/dfg/DictResponseWrapper.py | 3 - api/dfg/EateryToJson.py | 11 +- api/dfg/InMemoryCache.py | 7 +- api/dfg/assembly/AssembleEateries.py | 107 ++++++++++++ api/dfg/assembly/datatype/Eatery.py | 61 +++++++ api/dfg/{ => preparation}/CornellDiningNow.py | 26 +-- api/dfg/preparation/EateryStubs.py | 26 +++ api/dfg/{ => preparation}/ExternalEateries.py | 31 ++-- .../{ => preparation}/GoogleSheetsEateries.py | 18 +- .../datatype/CornellDiningEatery.py} | 23 ++- api/dfg/preparation/datatype/EateryStub.py | 5 + api/{ => dfg/preparation}/datatype/Event.py | 2 +- api/{ => dfg/preparation}/datatype/Menu.py | 2 +- .../preparation}/datatype/MenuCategory.py | 2 +- .../preparation}/datatype/MenuItem.py | 0 .../preparation/datatype/OverrideEatery.py | 61 +++++++ api/dfg/preparation/datatype/OverrideEvent.py | 63 +++++++ .../AddWaitTimesToEateries.py} | 84 ++++------ api/dfg/waittimes/FetchTransactionCounts.py | 37 +++++ .../datatype/EateryWithWaitTimes.py} | 4 +- api/{ => dfg/waittimes}/datatype/WaitTime.py | 0 .../waittimes}/datatype/WaitTimesByDay.py | 0 api/views.py | 57 ++++--- static_sources/cornell_eateries.json | 156 ++++++++++++++++++ 27 files changed, 649 insertions(+), 161 deletions(-) delete mode 100644 api/datatype/__init__.py delete mode 100644 api/dfg/Concat.py create mode 100644 api/dfg/assembly/AssembleEateries.py create mode 100644 api/dfg/assembly/datatype/Eatery.py rename api/dfg/{ => preparation}/CornellDiningNow.py (85%) create mode 100644 api/dfg/preparation/EateryStubs.py rename api/dfg/{ => preparation}/ExternalEateries.py (87%) rename api/dfg/{ => preparation}/GoogleSheetsEateries.py (91%) rename api/{datatype/Eatery.py => dfg/preparation/datatype/CornellDiningEatery.py} (73%) create mode 100644 api/dfg/preparation/datatype/EateryStub.py rename api/{ => dfg/preparation}/datatype/Event.py (97%) rename api/{ => dfg/preparation}/datatype/Menu.py (74%) rename api/{ => dfg/preparation}/datatype/MenuCategory.py (83%) rename api/{ => dfg/preparation}/datatype/MenuItem.py (100%) create mode 100644 api/dfg/preparation/datatype/OverrideEatery.py create mode 100644 api/dfg/preparation/datatype/OverrideEvent.py rename api/dfg/{CalculateWaitTimes.py => waittimes/AddWaitTimesToEateries.py} (59%) create mode 100644 api/dfg/waittimes/FetchTransactionCounts.py rename api/{datatype/EateryResult.py => dfg/waittimes/datatype/EateryWithWaitTimes.py} (91%) rename api/{ => dfg/waittimes}/datatype/WaitTime.py (100%) rename api/{ => dfg/waittimes}/datatype/WaitTimesByDay.py (100%) create mode 100644 static_sources/cornell_eateries.json diff --git a/api/datatype/__init__.py b/api/datatype/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/dfg/Concat.py b/api/dfg/Concat.py deleted file mode 100644 index cb780f3..0000000 --- a/api/dfg/Concat.py +++ /dev/null @@ -1,21 +0,0 @@ -from .DfgNode import DfgNode - - -class Concat(DfgNode): - - def __init__(self, children: list[DfgNode]): - self.children = children - - def __call__(self, *args, **kwargs): - result = [] - - for child in self.children: - result += child(*args, **kwargs) - - return result - - def children(self): - return self.children - - def description(self): - return "Concat" diff --git a/api/dfg/DfgNode.py b/api/dfg/DfgNode.py index 57d5821..d7615d6 100644 --- a/api/dfg/DfgNode.py +++ b/api/dfg/DfgNode.py @@ -3,8 +3,5 @@ class DfgNode: def __call__(self, *args, **kwargs): raise Exception() - def children(self): - return [] - def description(self): raise Exception() diff --git a/api/dfg/DictResponseWrapper.py b/api/dfg/DictResponseWrapper.py index 17fd978..618d89b 100644 --- a/api/dfg/DictResponseWrapper.py +++ b/api/dfg/DictResponseWrapper.py @@ -25,8 +25,5 @@ def __call__(self, *args, **kwargs): "error": str(e) } - def children(self): - return [self.child] - def description(self): return "DictResponseWrapper" diff --git a/api/dfg/EateryToJson.py b/api/dfg/EateryToJson.py index 8e0515c..6842c84 100644 --- a/api/dfg/EateryToJson.py +++ b/api/dfg/EateryToJson.py @@ -1,9 +1,7 @@ from typing import Union -from api.datatype.EateryResult import EateryResult +from api.dfg.waittimes.datatype.EateryWithWaitTimes import EateryWithWaitTimes from api.dfg.DfgNode import DfgNode - - class EateryToJson(DfgNode): def __init__(self, child: DfgNode): @@ -14,7 +12,7 @@ def __call__(self, *args, **kwargs): return EateryToJson.to_json(result, *args, **kwargs) @staticmethod - def to_json(obj: Union[list, dict, EateryResult], *args, **kwargs): + def to_json(obj: Union[list, dict, EateryWithWaitTimes], *args, **kwargs): if isinstance(obj, list): return [ EateryToJson.to_json(elem, *args, **kwargs) @@ -32,7 +30,4 @@ def to_json(obj: Union[list, dict, EateryResult], *args, **kwargs): tzinfo=kwargs.get("tzinfo"), start=kwargs.get("start"), end=kwargs.get("end") - ) - - def children(self): - return [self.child] + ) \ No newline at end of file diff --git a/api/dfg/InMemoryCache.py b/api/dfg/InMemoryCache.py index d106d59..b6a089d 100644 --- a/api/dfg/InMemoryCache.py +++ b/api/dfg/InMemoryCache.py @@ -33,7 +33,6 @@ def __init__(self, child, expiration: float = 3600, max_size: int = 5): self.max_size = max_size self.snapshots: list[DataSnapshot] = [] - def current_time(self): return time.time() @@ -49,8 +48,9 @@ def fifo_index(self): return oldest_snapshot_index def __call__(self, *args, **kwargs): + should_reload = kwargs.get("reload") for snapshot in self.snapshots: - if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): + if not should_reload and snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): print(f"{self}: Returning from cache") return snapshot.get_data() @@ -63,9 +63,6 @@ def __call__(self, *args, **kwargs): self.snapshots[index_to_replace] = new_snapshot return new_snapshot.get_data() - def children(self): - return [self.child] - def to_json(self, *args, **kwargs): for snapshot in self.snapshots: if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): diff --git a/api/dfg/assembly/AssembleEateries.py b/api/dfg/assembly/AssembleEateries.py new file mode 100644 index 0000000..d5e8acb --- /dev/null +++ b/api/dfg/assembly/AssembleEateries.py @@ -0,0 +1,107 @@ +# takes in api_data, google_sheets data, and stubs, and generates a list[Eatery] + +from typing import Optional, TypeVar +from api.dfg.DfgNode import DfgNode +from api.dfg.assembly.datatype.Eatery import Eatery +from api.dfg.preparation.datatype.EateryStub import EateryStub +from api.dfg.preparation.datatype.Event import Event +from api.dfg.preparation.datatype.OverrideEatery import OverrideEatery +from api.dfg.preparation.datatype.CornellDiningEatery import CornellDiningEatery +from api.dfg.preparation.datatype.OverrideEvent import OverrideEvent + +T = TypeVar('T') + +class AssembleEateries(DfgNode): + + def __init__(self, stubs: DfgNode, cornell_dining: DfgNode, override: DfgNode): + self.stubs = stubs + self.cornell_dining = cornell_dining + self.override = override + + def __call__(self, *args, **kwargs): + assembled_eateries = [] + eatery_stubs = self.stubs(*args, **kwargs) + cornell_dining_eateries = self.cornell_dining(*args, **kwargs) + override_eateries = self.override(*args, **kwargs) + + for stub in eatery_stubs: + cornell_dining_eatery = next((eatery for eatery in cornell_dining_eateries if eatery.name == stub.name), None) + override_eatery = next((eatery for eatery in override_eateries if eatery.name == stub.name), None) + assembled_eateries.append(AssembleEateries.preparation_to_eatery( + stub=stub, + cornell_dining_eatery=cornell_dining_eatery, + override_eatery=override_eatery + )) + return assembled_eateries + + @staticmethod + def preparation_to_eatery( + stub: EateryStub, + cornell_dining_eatery: Optional[CornellDiningEatery], + override_eatery: Optional[OverrideEatery] + ) -> Eatery: + return Eatery( + id=stub.id, + name=stub.name, + campus_area=AssembleEateries.fetch_field_precedence_first( + None if override_eatery is None else override_eatery.campus_area, + None if cornell_dining_eatery is None else cornell_dining_eatery.campus_area), + events=AssembleEateries.events_with_precedence( + None if override_eatery is None else override_eatery.known_events, + None if cornell_dining_eatery is None else cornell_dining_eatery.known_events), + latitude=AssembleEateries.fetch_field_precedence_first( + None if override_eatery is None else override_eatery.latitude, + None if cornell_dining_eatery is None else cornell_dining_eatery.latitude), + longitude=AssembleEateries.fetch_field_precedence_first( + None if override_eatery is None else override_eatery.longitude, + None if cornell_dining_eatery is None else cornell_dining_eatery.longitude), + payment_methods=AssembleEateries.fetch_field_precedence_first( + None if override_eatery is None else override_eatery.payment_methods, + None if cornell_dining_eatery is None else cornell_dining_eatery.payment_methods), + location=AssembleEateries.fetch_field_precedence_first( + None if override_eatery is None else override_eatery.location, + None if cornell_dining_eatery is None else cornell_dining_eatery.location), + online_order=AssembleEateries.fetch_field_precedence_first( + None if override_eatery is None else override_eatery.online_order, + None if cornell_dining_eatery is None else cornell_dining_eatery.online_order), + online_order_url=AssembleEateries.fetch_field_precedence_first( + None if override_eatery is None else override_eatery.online_order_url, + None if cornell_dining_eatery is None else cornell_dining_eatery.online_order_url) + ) + + @staticmethod + def events_with_precedence( + optional_override_events: Optional[list[OverrideEvent]], + optional_cornell_dining_events: Optional[list[Event]] + ): + override_events = [] if optional_override_events is None else optional_override_events + cornell_dining_events = [] if optional_cornell_dining_events is None else optional_cornell_dining_events + events = [] + + for event in cornell_dining_events: + potential_override_event = next((override_event for override_event in override_events if + override_event.canonical_date == event.canonical_date and + override_event.description == event.description + ), None) + if potential_override_event is not None: + events.append(event) + for event in override_events: + if event.exists: + events.append(Event( + description=event.description, + canonical_date=event.canonical_date, + start_timestamp=event.start_timestamp, + end_timestamp=event.end_timestamp, + menu=event.menu + )) + return events + + @staticmethod + def fetch_field_precedence_first( + primary: Optional[T], + secondary: Optional[T], + ) -> Optional[T]: + return secondary if primary is None else primary + + def description(self): + return "AssembleEateries" diff --git a/api/dfg/assembly/datatype/Eatery.py b/api/dfg/assembly/datatype/Eatery.py new file mode 100644 index 0000000..1cfe371 --- /dev/null +++ b/api/dfg/assembly/datatype/Eatery.py @@ -0,0 +1,61 @@ +from datetime import date +from typing import Optional + +import pytz + +from api.dfg.preparation.datatype.Event import Event, filter_range + +class Eatery: + + def __init__( + self, + id: int, + name: str, + campus_area: str, + events: list[Event], + latitude: float, + longitude: float, + payment_methods: list[str], + location: str, + online_order: bool, + online_order_url: str + ): + self.id = id + self.name = name + self.campus_area = campus_area + self.latitude = latitude + self.longitude = longitude + self.known_events = events + self.payment_methods = payment_methods + self.location = location + self.online_order = online_order + self.online_order_url = online_order_url + + def events( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None, + ): + return filter_range(self.known_events, tzinfo, start, end) + + def to_json( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None + ): + eatery = { + "id": self.id, + "name": self.name, + "campus_area": self.campus_area, + "events": [event.to_json() for event in self.events(tzinfo, start, end)], + "latitude": self.latitude, + "longitude": self.longitude, + "payment_methods": None if self.payment_methods is None else [payment_method for payment_method in self.payment_methods], + "location": self.location, + "online_order": self.online_order, + "online_order_url": self.online_order_url + } + return eatery + diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/preparation/CornellDiningNow.py similarity index 85% rename from api/dfg/CornellDiningNow.py rename to api/dfg/preparation/CornellDiningNow.py index 54b2700..e25b397 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/preparation/CornellDiningNow.py @@ -1,12 +1,12 @@ -from typing import Union +import requests from api.dfg.DfgNode import DfgNode -import requests -from api.datatype.Eatery import Eatery -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem +from api.dfg.preparation.datatype.CornellDiningEatery import CornellDiningEatery +from api.dfg.preparation.datatype.Event import Event +from api.dfg.preparation.datatype.Menu import Menu +from api.dfg.preparation.datatype.MenuCategory import MenuCategory +from api.dfg.preparation.datatype.MenuItem import MenuItem + from datetime import date @@ -14,7 +14,7 @@ class CornellDiningNow(DfgNode): CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" - def __call__(self, *args, **kwargs) -> list[Eatery]: + def __call__(self, *args, **kwargs) -> list[CornellDiningEatery]: try: response = requests.get(CornellDiningNow.CORNELL_DINING_URL).json() @@ -33,13 +33,15 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: raise Exception(response["message"]) @staticmethod - def parse_eatery(json_eatery: dict) -> Eatery: + def parse_eatery(json_eatery: dict) -> CornellDiningEatery: is_cafe = "Cafe" in { eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] } - return Eatery( + return CornellDiningEatery( name=json_eatery["name"], + about=json_eatery["about"], + about_short=json_eatery["aboutshort"], campus_area=json_eatery["campusArea"]["descrshort"], latitude=json_eatery["latitude"], longitude=json_eatery["longitude"], @@ -49,7 +51,9 @@ def parse_eatery(json_eatery: dict) -> Eatery: is_cafe = is_cafe ), payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), - online_order_url=json_eatery["onlineOrderUrl"] if json_eatery["onlineOrdering"] else None + location=json_eatery["location"], + online_order=json_eatery["onlineOrdering"], + online_order_url=json_eatery["onlineOrderUrl"] ) @staticmethod diff --git a/api/dfg/preparation/EateryStubs.py b/api/dfg/preparation/EateryStubs.py new file mode 100644 index 0000000..d3d4639 --- /dev/null +++ b/api/dfg/preparation/EateryStubs.py @@ -0,0 +1,26 @@ + +from api.dfg.DfgNode import DfgNode +from api.dfg.preparation.datatype.EateryStub import EateryStub + +import json + +class EateryStubs(DfgNode): + ALL_EATERIES_PATH = "static_sources/cornell_eateries.json" + + def __call__(self, *args, **kwargs) -> list[EateryStub]: + eateries = [] + + with open(EateryStubs.ALL_EATERIES_PATH) as f: + json_eateries = json.load(f)["eateries"] + + for json_eatery in json_eateries: + eateries.append(EateryStubs.eatery_from_json(json_eatery)) + + return eateries + + @staticmethod + def eatery_from_json(json_eatery: dict) -> EateryStub: + return EateryStub( + name = json_eatery["name"], + id = json_eatery["id"] + ) diff --git a/api/dfg/ExternalEateries.py b/api/dfg/preparation/ExternalEateries.py similarity index 87% rename from api/dfg/ExternalEateries.py rename to api/dfg/preparation/ExternalEateries.py index 0207d4a..7ca359e 100644 --- a/api/dfg/ExternalEateries.py +++ b/api/dfg/preparation/ExternalEateries.py @@ -5,14 +5,16 @@ import pytz from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem + +from api.dfg.preparation.datatype.OverrideEatery import OverrideEatery +from api.dfg.preparation.datatype.OverrideEvent import OverrideEvent +from api.dfg.preparation.datatype.Menu import Menu +from api.dfg.preparation.datatype.MenuCategory import MenuCategory +from api.dfg.preparation.datatype.MenuItem import MenuItem import json +# eventually need to deprecate this for a custom DB backend storing all of the overrides class ExternalEateries(DfgNode): # TODO: Make parsing of ExternalEateries the same as parsing of normal eateries, except from file, and then read external data on top of this @@ -29,7 +31,7 @@ class ExternalEateries(DfgNode): 'sunday' ] - def __call__(self, *args, **kwargs) -> list[Eatery]: + def __call__(self, *args, **kwargs) -> list[OverrideEatery]: eateries = [] with open(ExternalEateries.EXTERNAL_EATERIES_PATH) as f: @@ -48,8 +50,8 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: return eateries @staticmethod - def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date) -> Eatery: - return Eatery( + def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date) -> OverrideEatery: + return OverrideEatery( name=json_eatery["name"], campus_area=json_eatery["campusArea"]["descrshort"], events=ExternalEateries.eatery_events_from_json( @@ -62,7 +64,9 @@ def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date latitude=json_eatery["coordinates"]["latitude"], longitude=json_eatery["coordinates"]["longitude"], payment_methods=ExternalEateries.generate_payment_methods(json_eatery["payMethods"]), - online_order_url=json_eatery["onlineOrderUrl"] if json_eatery["onlineOrdering"] else None + location=json_eatery["location"], + online_order=json_eatery["onlineOrdering"], + online_order_url=json_eatery["onlineOrderUrl"] ) @@ -88,7 +92,7 @@ def eatery_events_from_json( json_dining_items: list, start_date: datetime.date, end_date: datetime.date - ) -> list[Event]: + ) -> list[OverrideEvent]: weekday_to_event_templates: dict[int: list[dict]] = {} for json_weekday_event_templates in json_operating_hours: @@ -111,12 +115,12 @@ def eatery_events_from_json( weekday_to_event_templates[weekday] = [] weekday_to_event_templates[weekday].append(event_template) - resolved_events: list[Event] = [] + resolved_events: list[OverrideEvent] = [] current = start_date while current <= end_date: if current.weekday() in weekday_to_event_templates: for event_template in weekday_to_event_templates[current.weekday()]: - event = Event( + event = OverrideEvent( description=event_template["descr"], canonical_date=current, menu=ExternalEateries.eatery_menu_from_json(json_dining_items), @@ -127,7 +131,8 @@ def eatery_events_from_json( end_timestamp=ExternalEateries.timestamp_combined( current, ExternalEateries.time_since_midnight(event_template["end"]) - ) + ), + exists=True ) resolved_events.append(event) diff --git a/api/dfg/GoogleSheetsEateries.py b/api/dfg/preparation/GoogleSheetsEateries.py similarity index 91% rename from api/dfg/GoogleSheetsEateries.py rename to api/dfg/preparation/GoogleSheetsEateries.py index 78a6f42..6d163e1 100644 --- a/api/dfg/GoogleSheetsEateries.py +++ b/api/dfg/preparation/GoogleSheetsEateries.py @@ -6,11 +6,11 @@ import pytz -from api.datatype.Eatery import Eatery -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem +from api.dfg.assembly.datatype.Eatery import Eatery +from api.dfg.preparation.datatype.Event import Event +from api.dfg.preparation.datatype.Menu import Menu +from api.dfg.preparation.datatype.MenuCategory import MenuCategory +from api.dfg.preparation.datatype.MenuItem import MenuItem from api.dfg.DfgNode import DfgNode import os.path @@ -39,11 +39,11 @@ def __init__( def __call__(self, *args, **kwargs): eateries = [] - with open(self.external_eateries_path) as f: - json_eateries = json.load(f)["eateries"] + # with open(self.external_eateries_path) as f: + # json_eateries = json.load(f)["eateries"] - for json_eatery in json_eateries: - eateries.append(self.eatery_from_json(json_eatery)) + # for json_eatery in json_eateries: + # eateries.append(self.eatery_from_json(json_eatery)) return eateries diff --git a/api/datatype/Eatery.py b/api/dfg/preparation/datatype/CornellDiningEatery.py similarity index 73% rename from api/datatype/Eatery.py rename to api/dfg/preparation/datatype/CornellDiningEatery.py index 7058ec9..4cda151 100644 --- a/api/datatype/Eatery.py +++ b/api/dfg/preparation/datatype/CornellDiningEatery.py @@ -1,31 +1,36 @@ from datetime import date -from typing import Mapping, Optional -from .WaitTime import WaitTime +from typing import Optional import pytz -from api.datatype.Event import Event, filter_range +from api.dfg.preparation.datatype.Event import Event, filter_range -class Eatery: +class CornellDiningEatery: def __init__( self, name: str, + about: str, + about_short: str, campus_area: str, events: list[Event], latitude: float, longitude: float, payment_methods: list[str], location: str, - online_order_url: Optional[str] = None + online_order: bool, + online_order_url: str ): self.name = name + self.about = about + self.about_short = about_short self.campus_area = campus_area self.latitude = latitude self.longitude = longitude self.known_events = events self.payment_methods = payment_methods self.location = location + self.online_order = online_order self.online_order_url = online_order_url def events( @@ -43,15 +48,17 @@ def to_json( end: Optional[date] = None ): eatery = { + "name": self.name, + "about": self.about, + "about_short": self.about_short, "campus_area": self.campus_area, "events": [event.to_json() for event in self.events(tzinfo, start, end)], "latitude": self.latitude, "longitude": self.longitude, - "name": self.name, "payment_methods": [payment_method for payment_method in self.payment_methods], "location": self.location, + "online_order": self.online_order, + "online_order_url": self.online_order_url } - if self.online_order_url is not None: - eatery["online_order_url"] = self.online_order_url return eatery diff --git a/api/dfg/preparation/datatype/EateryStub.py b/api/dfg/preparation/datatype/EateryStub.py new file mode 100644 index 0000000..90aeac6 --- /dev/null +++ b/api/dfg/preparation/datatype/EateryStub.py @@ -0,0 +1,5 @@ + +class EateryStub: + def __init__(self, name: str, id: int): + self.name = name + self.id = id diff --git a/api/datatype/Event.py b/api/dfg/preparation/datatype/Event.py similarity index 97% rename from api/datatype/Event.py rename to api/dfg/preparation/datatype/Event.py index 9083959..9736346 100644 --- a/api/datatype/Event.py +++ b/api/dfg/preparation/datatype/Event.py @@ -2,7 +2,7 @@ from datetime import date, datetime, time -from api.datatype.Menu import Menu +from api.dfg.preparation.datatype.Menu import Menu import pytz diff --git a/api/datatype/Menu.py b/api/dfg/preparation/datatype/Menu.py similarity index 74% rename from api/datatype/Menu.py rename to api/dfg/preparation/datatype/Menu.py index dd0f742..4623d72 100644 --- a/api/datatype/Menu.py +++ b/api/dfg/preparation/datatype/Menu.py @@ -1,4 +1,4 @@ -from api.datatype.MenuCategory import MenuCategory +from api.dfg.preparation.datatype.MenuCategory import MenuCategory class Menu: diff --git a/api/datatype/MenuCategory.py b/api/dfg/preparation/datatype/MenuCategory.py similarity index 83% rename from api/datatype/MenuCategory.py rename to api/dfg/preparation/datatype/MenuCategory.py index 3a2fee3..7347afb 100644 --- a/api/datatype/MenuCategory.py +++ b/api/dfg/preparation/datatype/MenuCategory.py @@ -1,4 +1,4 @@ -from api.datatype.MenuItem import MenuItem +from api.dfg.preparation.datatype.MenuItem import MenuItem class MenuCategory: diff --git a/api/datatype/MenuItem.py b/api/dfg/preparation/datatype/MenuItem.py similarity index 100% rename from api/datatype/MenuItem.py rename to api/dfg/preparation/datatype/MenuItem.py diff --git a/api/dfg/preparation/datatype/OverrideEatery.py b/api/dfg/preparation/datatype/OverrideEatery.py new file mode 100644 index 0000000..0fa58a4 --- /dev/null +++ b/api/dfg/preparation/datatype/OverrideEatery.py @@ -0,0 +1,61 @@ +from datetime import date +from typing import Optional + +import pytz + +from api.dfg.preparation.datatype.OverrideEvent import OverrideEvent, filter_range + +class OverrideEatery: + + def __init__( + self, + name: str, + about: Optional[str] = None, + campus_area: Optional[str] = None, + latitude: Optional[float] = None, + longitude: Optional[float] = None, + events: Optional[list[OverrideEvent]] = None, + payment_methods: Optional[list[str]] = None, + location: Optional[str] = None, + online_order: Optional[bool] = None, + online_order_url: Optional[str] = None + ): + self.name = name + self.about = about + self.campus_area = campus_area + self.latitude = latitude + self.longitude = longitude + self.known_events = events + self.payment_methods = payment_methods + self.location = location + self.online_order = online_order + self.online_order_url = online_order_url + + def events( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None, + ): + return filter_range(self.known_events, tzinfo, start, end) + + def to_json( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None + ): + eatery = { + "name": self.name, + "about": self.about, + "campus_area": self.campus_area, + "events": [event.to_json() for event in self.events(tzinfo, start, end)], + "latitude": self.latitude, + "longitude": self.longitude, + "payment_methods": [payment_method for payment_method in self.payment_methods], + "location": self.location, + "online_order": self.online_order, + "online_order_url": self.online_order_url + } + return eatery + diff --git a/api/dfg/preparation/datatype/OverrideEvent.py b/api/dfg/preparation/datatype/OverrideEvent.py new file mode 100644 index 0000000..bc3e877 --- /dev/null +++ b/api/dfg/preparation/datatype/OverrideEvent.py @@ -0,0 +1,63 @@ +from typing import Optional + +from datetime import date, datetime, time + +from api.dfg.preparation.datatype.Menu import Menu + +import pytz + +class OverrideEvent: + + # Pass in [exists = false] to remove events with the same description and canonical_date from occuring + def __init__( + self, + description: str, + canonical_date: date, + start_timestamp: int, + end_timestamp: int, + menu: Menu, + exists: bool + ): + self.description = description + self.canonical_date = canonical_date + self.start_timestamp = start_timestamp + self.end_timestamp = end_timestamp + self.menu = menu + self.exists = exists + + def to_json(self): + return { + "description": self.description, + "canonical_date": str(self.canonical_date), + "start_timestamp": self.start_timestamp, + "end_timestamp": self.end_timestamp, + "menu": self.menu.to_json() + } + + def __contains__(self, item: int): + return self.start_timestamp <= item <= self.end_timestamp + + +def _combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: + return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) + + +def filter_range(events: list[OverrideEvent], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): + if start is None and end is None: + return events + + elif tzinfo is not None and start is not None and end is None: + start_ts = _combined_timestamp(start, time(), tzinfo) + return [event for event in events if ( + (start_ts in event) or start == event.canonical_date + )] + + elif tzinfo is not None and start is not None and end is not None: + start_ts = _combined_timestamp(start, time(), tzinfo) + end_ts = _combined_timestamp(end, time(), tzinfo) + return [event for event in events if ( + (start_ts in event) or (end_ts in event) or start <= event.canonical_date <= end + )] + + else: + raise Exception(f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}") diff --git a/api/dfg/CalculateWaitTimes.py b/api/dfg/waittimes/AddWaitTimesToEateries.py similarity index 59% rename from api/dfg/CalculateWaitTimes.py rename to api/dfg/waittimes/AddWaitTimesToEateries.py index ec1efd0..33d0a24 100644 --- a/api/dfg/CalculateWaitTimes.py +++ b/api/dfg/waittimes/AddWaitTimesToEateries.py @@ -1,47 +1,34 @@ +# takes in api_data, google_sheets data, and stubs, and generates a list[Eatery] + from typing import Mapping -from api.datatype.Eatery import Eatery +from datetime import date, datetime +import pytz + +from api.dfg.DfgNode import DfgNode +from api.dfg.assembly.datatype.Eatery import Eatery from api.dfg.DfgNode import DfgNode -from transactions.models import TransactionHistory +from api.dfg.waittimes.datatype.EateryWithWaitTimes import EateryWithWaitTimes +from api.dfg.waittimes.datatype.WaitTime import WaitTime +from api.dfg.waittimes.datatype.WaitTimesByDay import WaitTimesByDay + from transactions.serializers import TransactionHistorySerializer -from ..datatype.EateryResult import EateryResult -from ..datatype.WaitTime import WaitTime -from ..datatype.WaitTimesByDay import WaitTimesByDay -from django.db.models import Avg -from datetime import date, datetime, timedelta, tzinfo -import pytz -class CalculateWaitTimes(DfgNode): - - def __init__(self, child: DfgNode): - self.child = child - - def __call__(self, *args, **kwargs) -> list[EateryResult]: - eatery_results = [] - transactions_by_date = {} # [date]: {[eateryname]: list[TransactionHistory]} - past_days = [] - date = kwargs.get("start") - while date <= kwargs.get("end"): - # We only calculate the wait times for this first day - transactions_on_date = {} - for i in range(1, 13): - # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the transaction_count - past_day = date - timedelta(days = 7 * i) - past_days.append(past_day) - transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ - .values("name", "block_end_time") \ - .annotate(transaction_avg=Avg("transaction_count")) - for unit in transaction_avg_counts: - transaction_history = TransactionHistorySerializer(unit) - eatery_name = transaction_history.data['name'] - if eatery_name not in transactions_on_date: - transactions_on_date[eatery_name] = [] - transactions_on_date[eatery_name].append(transaction_history) - transactions_by_date[date] = transactions_on_date - date += timedelta(days=1) - for eatery in self.child(*args, **kwargs): - eatery_results.append(CalculateWaitTimes.generate_eatery_result(eatery, transactions_by_date)) - - return eatery_results + +class AddWaitTimesToEateries(DfgNode): + + def __init__(self, eateries: DfgNode, transaction_counts: DfgNode): + self.eateries = eateries + self.transaction_counts = transaction_counts + + def __call__(self, *args, **kwargs): + results = [] + transactions_by_date = self.transaction_counts(*args, **kwargs) + + for eatery in self.eateries(*args, **kwargs): + results.append(AddWaitTimesToEateries.generate_eatery_result(eatery, transactions_by_date)) + + return results + # Expected amount of time (in seconds) for the length of the line to decrease by 1 person # Returns [lower, expected, upper] @@ -79,8 +66,8 @@ def generate_eatery_wait_times_by_day( wait_times = [] customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] for index in reversed(range(0, len(transactions))): - base_times = CalculateWaitTimes.BASE_TIME_TO_GET_FOOD(eatery.name) - line_decrease_times = CalculateWaitTimes.LINE_DECREASE_BY_ONE_TIME(eatery.name) + base_times = AddWaitTimesToEateries.BASE_TIME_TO_GET_FOOD(eatery.name) + line_decrease_times = AddWaitTimesToEateries.LINE_DECREASE_BY_ONE_TIME(eatery.name) # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) @@ -95,7 +82,7 @@ def generate_eatery_wait_times_by_day( customers_waiting_in_line.append(0.0) block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() - timestamp = int(CalculateWaitTimes.timestamp_combined(date, block_end_time) - 5 * 60 / 2) + timestamp = int(AddWaitTimesToEateries.timestamp_combined(date, block_end_time) - 5 * 60 / 2) if any([timestamp in event for event in eatery.events()]): wait_times.insert(0, WaitTime( timestamp=timestamp, @@ -109,12 +96,12 @@ def generate_eatery_wait_times_by_day( def generate_eatery_result( eatery: Eatery, transactions_by_date: Mapping[date, Mapping[str, list[TransactionHistorySerializer]]] - ) -> EateryResult: + ) -> EateryWithWaitTimes: eatery_wait_times_by_day = [] for date in transactions_by_date: if eatery.name in transactions_by_date[date]: - eatery_wait_times_by_day.append(CalculateWaitTimes.generate_eatery_wait_times_by_day(eatery, date, transactions_by_date[date][eatery.name])) - return EateryResult(eatery, eatery_wait_times_by_day) + eatery_wait_times_by_day.append(AddWaitTimesToEateries.generate_eatery_wait_times_by_day(eatery, date, transactions_by_date[date][eatery.name])) + return EateryWithWaitTimes(eatery, eatery_wait_times_by_day) @staticmethod def timestamp_combined(date: datetime.date, time: datetime.time): @@ -126,8 +113,5 @@ def timestamp_combined(date: datetime.date, time: datetime.time): tz = pytz.timezone('America/New_York') return int(tz.localize(datetime.combine(date, time)).timestamp()) - def children(self): - return [self.child] - def description(self): - return "CalculateWaitTimes" + return "AddWaitTimesToEateries" diff --git a/api/dfg/waittimes/FetchTransactionCounts.py b/api/dfg/waittimes/FetchTransactionCounts.py new file mode 100644 index 0000000..becc04a --- /dev/null +++ b/api/dfg/waittimes/FetchTransactionCounts.py @@ -0,0 +1,37 @@ +from typing import Mapping +from django.db.models import Avg +from datetime import date, timedelta + +from api.dfg.DfgNode import DfgNode +from transactions.models import TransactionHistory +from transactions.serializers import TransactionHistorySerializer + +class FetchTransactionCounts(DfgNode): + + def __call__(self, *args, **kwargs) -> Mapping[date, Mapping[str, list[TransactionHistory]]]: + transactions_by_date = {} + past_days = [] + date = kwargs.get("start") + while date <= kwargs.get("end"): + # We only calculate the wait times for this first day + transactions_on_date = {} + for i in range(1, 13): + # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the transaction_count + past_day = date - timedelta(days = 7 * i) + past_days.append(past_day) + transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ + .values("name", "block_end_time") \ + .annotate(transaction_avg=Avg("transaction_count")) + for unit in transaction_avg_counts: + transaction_history = TransactionHistorySerializer(unit) + eatery_name = transaction_history.data['name'] + if eatery_name not in transactions_on_date: + transactions_on_date[eatery_name] = [] + transactions_on_date[eatery_name].append(transaction_history) + transactions_by_date[date] = transactions_on_date + date += timedelta(days=1) + + return transactions_by_date + + def description(self): + return "FetchTransactionCounts" diff --git a/api/datatype/EateryResult.py b/api/dfg/waittimes/datatype/EateryWithWaitTimes.py similarity index 91% rename from api/datatype/EateryResult.py rename to api/dfg/waittimes/datatype/EateryWithWaitTimes.py index a0e9fcf..16d0bc3 100644 --- a/api/datatype/EateryResult.py +++ b/api/dfg/waittimes/datatype/EateryWithWaitTimes.py @@ -2,12 +2,12 @@ from datetime import date from typing import Mapping, Optional -from .Eatery import Eatery +from ...assembly.datatype.Eatery import Eatery from .WaitTimesByDay import WaitTimesByDay import pytz -class EateryResult: +class EateryWithWaitTimes: def __init__( self, diff --git a/api/datatype/WaitTime.py b/api/dfg/waittimes/datatype/WaitTime.py similarity index 100% rename from api/datatype/WaitTime.py rename to api/dfg/waittimes/datatype/WaitTime.py diff --git a/api/datatype/WaitTimesByDay.py b/api/dfg/waittimes/datatype/WaitTimesByDay.py similarity index 100% rename from api/datatype/WaitTimesByDay.py rename to api/dfg/waittimes/datatype/WaitTimesByDay.py diff --git a/api/views.py b/api/views.py index 91577b2..cb0c592 100644 --- a/api/views.py +++ b/api/views.py @@ -2,24 +2,29 @@ import pytz from django.http import JsonResponse -from api.dfg.CalculateWaitTimes import CalculateWaitTimes +from api.dfg.waittimes.FetchTransactionCounts import FetchTransactionCounts + +from api.dfg.preparation.CornellDiningNow import CornellDiningNow +from api.dfg.preparation.EateryStubs import EateryStubs +from api.dfg.preparation.ExternalEateries import ExternalEateries + +from api.dfg.assembly.AssembleEateries import AssembleEateries +from api.dfg.waittimes.AddWaitTimesToEateries import AddWaitTimesToEateries -from api.dfg.Concat import Concat -from api.dfg.CornellDiningNow import CornellDiningNow from api.dfg.DictResponseWrapper import DictResponseWrapper from api.dfg.EateryToJson import EateryToJson -from api.dfg.ExternalEateries import ExternalEateries -from api.dfg.GoogleSheetsEateries import GoogleSheetsEateries from api.dfg.InMemoryCache import InMemoryCache dataflow_graph = DictResponseWrapper( EateryToJson( InMemoryCache( - CalculateWaitTimes( - Concat([ - CornellDiningNow(), - ExternalEateries() - ]) + AddWaitTimesToEateries( + eateries=AssembleEateries( + stubs=EateryStubs(), + cornell_dining=CornellDiningNow(), + override=ExternalEateries() + ), + transaction_counts = FetchTransactionCounts() ) ) ), @@ -29,26 +34,28 @@ def index(request): tzinfo = pytz.timezone("US/Eastern") + reload = request.GET.get('reload') result = dataflow_graph( tzinfo=tzinfo, + reload=reload is not None and reload is not "false", start=date.today(), end=date.today() + timedelta(days=7) ) return JsonResponse(result) def google_sheets_eateries(request): - dfg = DictResponseWrapper( - EateryToJson( - GoogleSheetsEateries( - spreadsheet_id="1ImfeTUA6I1Ub-aavgIW53Pf7EVB694f1294NPSCRd5c" - ) - ) - ) - - result = dfg( - tzinfo=pytz.timezone("US/Eastern"), - start=date.today(), - end=date.today() + timedelta(days=7) - ) - - return JsonResponse(result) \ No newline at end of file + # dfg = DictResponseWrapper( + # EateryToJson( + # GoogleSheetsEateries( + # spreadsheet_id="1ImfeTUA6I1Ub-aavgIW53Pf7EVB694f1294NPSCRd5c" + # ) + # ) + # ) + + # result = dfg( + # tzinfo=pytz.timezone("US/Eastern"), + # start=date.today(), + # end=date.today() + timedelta(days=7) + # ) + + return JsonResponse([]) \ No newline at end of file diff --git a/static_sources/cornell_eateries.json b/static_sources/cornell_eateries.json new file mode 100644 index 0000000..ad2c94f --- /dev/null +++ b/static_sources/cornell_eateries.json @@ -0,0 +1,156 @@ +{ + "eateries": [ + { + "id": 1, + "name": "104West!" + }, + { + "id": 2, + "name": "Amit Bhatia Libe Café" + }, + { + "id": 3, + "name": "Atrium Café" + }, + { + "id": 4, + "name": "Bear Necessities Grill & C-Store" + }, + { + "id": 5, + "name": "Becker House Dining Room" + }, + { + "id": 6, + "name": "Big Red Barn" + }, + { + "id": 7, + "name": "Bus Stop Bagels" + }, + { + "id": 8, + "name": "Café Jennie" + }, + { + "id": 9, + "name": "Carol's Café" + }, + { + "id": 10, + "name": "Cook House Dining Room" + }, + { + "id": 11, + "name": "Cornell Dairy Bar" + }, + { + "id": 12, + "name": "Crossings Café" + }, + { + "id": 13, + "name": "Franny's" + }, + { + "id": 14, + "name": "Goldie's Café" + }, + { + "id": 15, + "name": "Green Dragon" + }, + { + "id": 16, + "name": "Hot Dog Cart" + }, + { + "id": 17, + "name": "Ice Cream Bike" + }, + { + "id": 18, + "name": "Jansen's Dining Room at Bethe House" + }, + { + "id": 19, + "name": "Jansen's Market" + }, + { + "id": 20, + "name": "Keeton House Dining Room" + }, + { + "id": 21, + "name": "Mann Café" + }, + { + "id": 22, + "name": "Martha's Café" + }, + { + "id": 23, + "name": "Mattin's Café" + }, + { + "id": 24, + "name": "McCormick's at Moakley House" + }, + { + "id": 25, + "name": "North Star Dining Room" + }, + { + "id": 26, + "name": "Okenshields" + }, + { + "id": 27, + "name": "Risley Dining Room" + }, + { + "id": 28, + "name": "Robert Purcell Marketplace Eatery" + }, + { + "id": 29, + "name": "Rose House Dining Room" + }, + { + "id": 30, + "name": "Rusty's" + }, + { + "id": 31, + "name": "Straight from the Market" + }, + { + "id": 32, + "name": "Trillium" + }, + { + "id": 33, + "name": "Terrace Restaurant" + }, + { + "id": 34, + "name": "Mac's Café" + }, + { + "id": 35, + "name": "Temple of Zeus" + }, + { + "id": 36, + "name": "Gimme Coffee" + }, + { + "id": 37, + "name": "Louie's Lunch" + }, + { + "id": 38, + "name": "Anabel's Grocery" + } + ] +} \ No newline at end of file From 256ae6a3f58707db7fed184191cd0ca2ddff2e19 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 3 Jan 2022 16:05:05 -0500 Subject: [PATCH 022/305] Refactor to id system. refactor to only use Eatery type [IN PROGRESS] --- api/datatype/Eatery.py | 106 ++++++++++++ api/{dfg/preparation => }/datatype/Event.py | 9 +- api/{dfg/preparation => }/datatype/Menu.py | 2 +- .../preparation => }/datatype/MenuCategory.py | 2 +- .../preparation => }/datatype/MenuItem.py | 0 api/{dfg/waittimes => }/datatype/WaitTime.py | 0 .../waittimes => }/datatype/WaitTimesByDay.py | 6 +- api/dfg/{assembly => }/AssembleEateries.py | 4 +- api/dfg/{preparation => }/CornellDiningNow.py | 88 +++++++++- api/dfg/DfgNode.py | 3 + api/dfg/EateryStubs.py | 8 + api/dfg/{preparation => }/ExternalEateries.py | 23 +-- .../{preparation => }/GoogleSheetsEateries.py | 10 +- api/dfg/LeftMerge.py | 84 ++++++++++ api/dfg/WaitTimes.py | 136 +++++++++++++++ api/dfg/assembly/datatype/Eatery.py | 61 ------- api/dfg/preparation/EateryStubs.py | 26 --- .../datatype/CornellDiningEatery.py | 64 ------- api/dfg/preparation/datatype/EateryStub.py | 5 - .../preparation/datatype/OverrideEatery.py | 61 ------- api/dfg/preparation/datatype/OverrideEvent.py | 63 ------- api/dfg/waittimes/AddWaitTimesToEateries.py | 125 -------------- api/dfg/waittimes/FetchTransactionCounts.py | 37 ----- .../waittimes/datatype/EateryWithWaitTimes.py | 28 ---- api/utils.py | 4 + api/views.py | 12 +- eateries/__init__.py | 0 eateries/admin.py | 3 + eateries/apps.py | 6 + eateries/migrations/__init__.py | 0 eateries/models.py | 11 ++ eateries/tests.py | 3 + eateries/views.py | 3 + eatery_blue_backend/settings.py | 3 +- static_sources/cornell_eateries.json | 156 ------------------ static_sources/external_eateries.json | 6 + .../update_transactions_controller.py | 74 ++++----- transactions/models.py | 4 +- 38 files changed, 528 insertions(+), 708 deletions(-) create mode 100644 api/datatype/Eatery.py rename api/{dfg/preparation => }/datatype/Event.py (93%) rename api/{dfg/preparation => }/datatype/Menu.py (74%) rename api/{dfg/preparation => }/datatype/MenuCategory.py (83%) rename api/{dfg/preparation => }/datatype/MenuItem.py (100%) rename api/{dfg/waittimes => }/datatype/WaitTime.py (100%) rename api/{dfg/waittimes => }/datatype/WaitTimesByDay.py (60%) rename api/dfg/{assembly => }/AssembleEateries.py (98%) rename api/dfg/{preparation => }/CornellDiningNow.py (64%) create mode 100644 api/dfg/EateryStubs.py rename api/dfg/{preparation => }/ExternalEateries.py (91%) rename api/dfg/{preparation => }/GoogleSheetsEateries.py (95%) create mode 100644 api/dfg/LeftMerge.py create mode 100644 api/dfg/WaitTimes.py delete mode 100644 api/dfg/assembly/datatype/Eatery.py delete mode 100644 api/dfg/preparation/EateryStubs.py delete mode 100644 api/dfg/preparation/datatype/CornellDiningEatery.py delete mode 100644 api/dfg/preparation/datatype/EateryStub.py delete mode 100644 api/dfg/preparation/datatype/OverrideEatery.py delete mode 100644 api/dfg/preparation/datatype/OverrideEvent.py delete mode 100644 api/dfg/waittimes/AddWaitTimesToEateries.py delete mode 100644 api/dfg/waittimes/FetchTransactionCounts.py delete mode 100644 api/dfg/waittimes/datatype/EateryWithWaitTimes.py create mode 100644 api/utils.py create mode 100644 eateries/__init__.py create mode 100644 eateries/admin.py create mode 100644 eateries/apps.py create mode 100644 eateries/migrations/__init__.py create mode 100644 eateries/models.py create mode 100644 eateries/tests.py create mode 100644 eateries/views.py delete mode 100644 static_sources/cornell_eateries.json diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py new file mode 100644 index 0000000..b0fa8bb --- /dev/null +++ b/api/datatype/Eatery.py @@ -0,0 +1,106 @@ +from datetime import date +from typing import Optional +from enum import Enum + +import pytz + +from api.datatype.Event import Event, filter_range +from api.datatype.WaitTimesByDay import WaitTimesByDay + +class EateryID(Enum): + NULL = 0, + ONE_ZERO_FOUR_WEST = 1, + LIBE_CAFE = 2, + ATRIUM_CAFE = 3, + BEAR_NECESSITIES = 4, + BECKER_HOUSE = 5, + BIG_RED_BARN = 6, + BUS_STOP_BAGELS = 7, + CAFE_JENNIE = 8, + CAROLS_CAFE = 9, + COOK_HOUSE = 10, + DAIRY_BAR = 11, + CROSSINGS_CAFE = 12 + FRANNYS = 13, + GOLDIES_CAFE = 14, + GREEN_DRAGON = 15, + HOT_DOG_CART = 16, + ICE_CREAM_BIKE = 17, + BETHE_HOUSE = 18, + JANSENS_MARKET = 19, + KEETON_HOUSE = 20, + MANN_CAFE = 21, + MARTHAS_CAFE = 22, + MATTINS_CAFE = 23, + MCCORMICKS = 24, + NORTH_STAR_DINING = 25, + OKENSHIELDS = 26, + RISLEY = 27, + RPCC = 28, + ROSE_HOUSE = 29, + RUSTYS = 30, + STRAIGHT_FROM_THE_MARKET = 31, + TRILLIUM = 32, + TERRACE = 33, + MACS_CAFE = 34, + TEMPLE_OF_ZEUS = 35, + GIMME_COFFEE = 36, + LOUIES = 37, + ANABELS_GROCERY = 38 + +class Eatery: + + def __init__( + self, + id: EateryID, + name: Optional[str] = None, + campus_area: Optional[str] = None, + events: Optional[list[Event]] = None, + latitude: Optional[float] = None, + longitude: Optional[float] = None, + payment_methods: Optional[list[str]] = None, + location: Optional[str] = None, + online_order: Optional[bool] = None, + online_order_url: Optional[str] = None, + wait_times: Optional[list[WaitTimesByDay]] = None + ): + self.id = id + self.name = name + self.campus_area = campus_area + self.latitude = latitude + self.longitude = longitude + self.known_events = events + self.payment_methods = payment_methods + self.location = location + self.online_order = online_order + self.online_order_url = online_order_url + self.wait_times = wait_times + + def events( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None, + ): + return filter_range(self.known_events, tzinfo, start, end) + + def to_json( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None + ): + eatery = { + "id": self.id.value, + "name": self.name, + "campus_area": self.campus_area, + "events": [event.to_json() for event in self.events(tzinfo, start, end)], + "latitude": self.latitude, + "longitude": self.longitude, + "payment_methods": None if self.payment_methods is None else [payment_method for payment_method in self.payment_methods], + "location": self.location, + "online_order": self.online_order, + "online_order_url": self.online_order_url, + "wait_times": [wait_time.to_json() for wait_time in self.wait_times] + } + return eatery diff --git a/api/dfg/preparation/datatype/Event.py b/api/datatype/Event.py similarity index 93% rename from api/dfg/preparation/datatype/Event.py rename to api/datatype/Event.py index 9736346..4125a6d 100644 --- a/api/dfg/preparation/datatype/Event.py +++ b/api/datatype/Event.py @@ -2,11 +2,10 @@ from datetime import date, datetime, time -from api.dfg.preparation.datatype.Menu import Menu +from api.datatype.Menu import Menu import pytz - class Event: def __init__( @@ -15,13 +14,15 @@ def __init__( canonical_date: date, start_timestamp: int, end_timestamp: int, - menu: Menu + menu: Menu, + exists: bool ): self.description = description self.canonical_date = canonical_date self.start_timestamp = start_timestamp self.end_timestamp = end_timestamp - self.menu = menu + self.menu = menu, + self.exists = exists def to_json(self): return { diff --git a/api/dfg/preparation/datatype/Menu.py b/api/datatype/Menu.py similarity index 74% rename from api/dfg/preparation/datatype/Menu.py rename to api/datatype/Menu.py index 4623d72..dd0f742 100644 --- a/api/dfg/preparation/datatype/Menu.py +++ b/api/datatype/Menu.py @@ -1,4 +1,4 @@ -from api.dfg.preparation.datatype.MenuCategory import MenuCategory +from api.datatype.MenuCategory import MenuCategory class Menu: diff --git a/api/dfg/preparation/datatype/MenuCategory.py b/api/datatype/MenuCategory.py similarity index 83% rename from api/dfg/preparation/datatype/MenuCategory.py rename to api/datatype/MenuCategory.py index 7347afb..3a2fee3 100644 --- a/api/dfg/preparation/datatype/MenuCategory.py +++ b/api/datatype/MenuCategory.py @@ -1,4 +1,4 @@ -from api.dfg.preparation.datatype.MenuItem import MenuItem +from api.datatype.MenuItem import MenuItem class MenuCategory: diff --git a/api/dfg/preparation/datatype/MenuItem.py b/api/datatype/MenuItem.py similarity index 100% rename from api/dfg/preparation/datatype/MenuItem.py rename to api/datatype/MenuItem.py diff --git a/api/dfg/waittimes/datatype/WaitTime.py b/api/datatype/WaitTime.py similarity index 100% rename from api/dfg/waittimes/datatype/WaitTime.py rename to api/datatype/WaitTime.py diff --git a/api/dfg/waittimes/datatype/WaitTimesByDay.py b/api/datatype/WaitTimesByDay.py similarity index 60% rename from api/dfg/waittimes/datatype/WaitTimesByDay.py rename to api/datatype/WaitTimesByDay.py index c62eed2..6356a35 100644 --- a/api/dfg/waittimes/datatype/WaitTimesByDay.py +++ b/api/datatype/WaitTimesByDay.py @@ -6,14 +6,14 @@ class WaitTimesByDay: def __init__( self, canonical_date: str, - wait_times: list[WaitTime] + daily_wait_times: list[WaitTime] ): self.canonical_date = canonical_date - self.wait_times = wait_times + self.daily_wait_times = daily_wait_times def to_json(self): return { "canonical_date": self.canonical_date, - "wait_times": [wait_time.to_json() for wait_time in self.wait_times] + "daily_wait_times": [wait_time.to_json() for wait_time in self.daily_wait_times] } diff --git a/api/dfg/assembly/AssembleEateries.py b/api/dfg/AssembleEateries.py similarity index 98% rename from api/dfg/assembly/AssembleEateries.py rename to api/dfg/AssembleEateries.py index d5e8acb..ba6d6a7 100644 --- a/api/dfg/assembly/AssembleEateries.py +++ b/api/dfg/AssembleEateries.py @@ -2,9 +2,9 @@ from typing import Optional, TypeVar from api.dfg.DfgNode import DfgNode -from api.dfg.assembly.datatype.Eatery import Eatery +from api.datatype.Eatery import Eatery from api.dfg.preparation.datatype.EateryStub import EateryStub -from api.dfg.preparation.datatype.Event import Event +from api.datatype.Event import Event from api.dfg.preparation.datatype.OverrideEatery import OverrideEatery from api.dfg.preparation.datatype.CornellDiningEatery import CornellDiningEatery from api.dfg.preparation.datatype.OverrideEvent import OverrideEvent diff --git a/api/dfg/preparation/CornellDiningNow.py b/api/dfg/CornellDiningNow.py similarity index 64% rename from api/dfg/preparation/CornellDiningNow.py rename to api/dfg/CornellDiningNow.py index e25b397..eec8571 100644 --- a/api/dfg/preparation/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -1,11 +1,12 @@ import requests from api.dfg.DfgNode import DfgNode -from api.dfg.preparation.datatype.CornellDiningEatery import CornellDiningEatery -from api.dfg.preparation.datatype.Event import Event -from api.dfg.preparation.datatype.Menu import Menu -from api.dfg.preparation.datatype.MenuCategory import MenuCategory -from api.dfg.preparation.datatype.MenuItem import MenuItem + +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem from datetime import date @@ -14,7 +15,7 @@ class CornellDiningNow(DfgNode): CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" - def __call__(self, *args, **kwargs) -> list[CornellDiningEatery]: + def __call__(self, *args, **kwargs) -> list[Eatery]: try: response = requests.get(CornellDiningNow.CORNELL_DINING_URL).json() @@ -33,13 +34,13 @@ def __call__(self, *args, **kwargs) -> list[CornellDiningEatery]: raise Exception(response["message"]) @staticmethod - def parse_eatery(json_eatery: dict) -> CornellDiningEatery: + def parse_eatery(json_eatery: dict) -> Eatery: is_cafe = "Cafe" in { eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] } - return CornellDiningEatery( - name=json_eatery["name"], + return Eatery( + id=CornellDiningNow.dining_id_to_internal_id(json_eatery["id"]), about=json_eatery["about"], about_short=json_eatery["aboutshort"], campus_area=json_eatery["campusArea"]["descrshort"], @@ -132,5 +133,74 @@ def dining_hall_menu_from_json(json_menu: list) -> Menu: return Menu(categories=menu_categories) + @staticmethod + def dining_id_to_internal_id(id: int): + if id == 31: + return EateryID.ONE_ZERO_FOUR_WEST + elif id == 7: + return EateryID.LIBE_CAFE + elif id == 8: + return EateryID.ATRIUM_CAFE + elif id == 1: + return EateryID.BEAR_NECESSITIES + elif id == 25: + return EateryID.BECKER_HOUSE + elif id == 10: + return EateryID.BIG_RED_BARN + elif id == 11: + return EateryID.BUS_STOP_BAGELS + elif id == 12: + return EateryID.CAFE_JENNIE + elif id == 2: + return EateryID.CAROLS_CAFE + elif id == 26: + return EateryID.COOK_HOUSE + elif id == 14: + return EateryID.DAIRY_BAR + elif id == 41: + return EateryID.CROSSINGS_CAFE + elif id == 32: + return EateryID.FRANNYS + elif id == 16: + return EateryID.GOLDIES_CAFE + elif id == 15: + return EateryID.GREEN_DRAGON + elif id == 24: + return EateryID.HOT_DOG_CART + elif id == 34: + return EateryID.ICE_CREAM_BIKE + elif id == 27: + return EateryID.BETHE_HOUSE + elif id == 28: + return EateryID.JANSENS_MARKET + elif id == 29: + return EateryID.KEETON_HOUSE + elif id == 42: + return EateryID.MANN_CAFE + elif id == 18: + return EateryID.MARTHAS_CAFE + elif id == 19: + return EateryID.MATTINS_CAFE + elif id == 33: + return EateryID.MCCORMICKS + elif id == 3: + return EateryID.NORTH_STAR_DINING + elif id == 20: + return EateryID.OKENSHIELDS + elif id == 4: + return EateryID.RISLEY + elif id == 5: + return EateryID.RPCC + elif id == 30: + return EateryID.ROSE_HOUSE + elif id == 21: + return EateryID.ROSE_HOUSE + elif id == 13: + return EateryID.STRAIGHT_FROM_THE_MARKET + elif id == 23: + return EateryID.TERRACE + else: + return EateryID.NULL + def description(self): return "CornellDiningNow" diff --git a/api/dfg/DfgNode.py b/api/dfg/DfgNode.py index d7615d6..373d86a 100644 --- a/api/dfg/DfgNode.py +++ b/api/dfg/DfgNode.py @@ -3,5 +3,8 @@ class DfgNode: def __call__(self, *args, **kwargs): raise Exception() + def children(self): + return [] + def description(self): raise Exception() diff --git a/api/dfg/EateryStubs.py b/api/dfg/EateryStubs.py new file mode 100644 index 0000000..9a02705 --- /dev/null +++ b/api/dfg/EateryStubs.py @@ -0,0 +1,8 @@ + +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID + +class EateryStubs(DfgNode): + + def __call__(self, *args, **kwargs) -> list[Eatery]: + return [Eatery(id = id) for id in EateryID] diff --git a/api/dfg/preparation/ExternalEateries.py b/api/dfg/ExternalEateries.py similarity index 91% rename from api/dfg/preparation/ExternalEateries.py rename to api/dfg/ExternalEateries.py index 7ca359e..2d04750 100644 --- a/api/dfg/preparation/ExternalEateries.py +++ b/api/dfg/ExternalEateries.py @@ -6,11 +6,11 @@ from api.dfg.DfgNode import DfgNode -from api.dfg.preparation.datatype.OverrideEatery import OverrideEatery -from api.dfg.preparation.datatype.OverrideEvent import OverrideEvent -from api.dfg.preparation.datatype.Menu import Menu -from api.dfg.preparation.datatype.MenuCategory import MenuCategory -from api.dfg.preparation.datatype.MenuItem import MenuItem +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem import json @@ -31,7 +31,7 @@ class ExternalEateries(DfgNode): 'sunday' ] - def __call__(self, *args, **kwargs) -> list[OverrideEatery]: + def __call__(self, *args, **kwargs) -> list[Eatery]: eateries = [] with open(ExternalEateries.EXTERNAL_EATERIES_PATH) as f: @@ -50,8 +50,9 @@ def __call__(self, *args, **kwargs) -> list[OverrideEatery]: return eateries @staticmethod - def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date) -> OverrideEatery: - return OverrideEatery( + def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date) -> Eatery: + return Eatery( + id = EateryID(json_eatery["id"]), name=json_eatery["name"], campus_area=json_eatery["campusArea"]["descrshort"], events=ExternalEateries.eatery_events_from_json( @@ -92,7 +93,7 @@ def eatery_events_from_json( json_dining_items: list, start_date: datetime.date, end_date: datetime.date - ) -> list[OverrideEvent]: + ) -> list[Event]: weekday_to_event_templates: dict[int: list[dict]] = {} for json_weekday_event_templates in json_operating_hours: @@ -115,12 +116,12 @@ def eatery_events_from_json( weekday_to_event_templates[weekday] = [] weekday_to_event_templates[weekday].append(event_template) - resolved_events: list[OverrideEvent] = [] + resolved_events: list[Event] = [] current = start_date while current <= end_date: if current.weekday() in weekday_to_event_templates: for event_template in weekday_to_event_templates[current.weekday()]: - event = OverrideEvent( + event = Event( description=event_template["descr"], canonical_date=current, menu=ExternalEateries.eatery_menu_from_json(json_dining_items), diff --git a/api/dfg/preparation/GoogleSheetsEateries.py b/api/dfg/GoogleSheetsEateries.py similarity index 95% rename from api/dfg/preparation/GoogleSheetsEateries.py rename to api/dfg/GoogleSheetsEateries.py index 6d163e1..5267c6f 100644 --- a/api/dfg/preparation/GoogleSheetsEateries.py +++ b/api/dfg/GoogleSheetsEateries.py @@ -6,11 +6,11 @@ import pytz -from api.dfg.assembly.datatype.Eatery import Eatery -from api.dfg.preparation.datatype.Event import Event -from api.dfg.preparation.datatype.Menu import Menu -from api.dfg.preparation.datatype.MenuCategory import MenuCategory -from api.dfg.preparation.datatype.MenuItem import MenuItem +from api.datatype.Eatery import Eatery +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem from api.dfg.DfgNode import DfgNode import os.path diff --git a/api/dfg/LeftMerge.py b/api/dfg/LeftMerge.py new file mode 100644 index 0000000..2bd2425 --- /dev/null +++ b/api/dfg/LeftMerge.py @@ -0,0 +1,84 @@ +from typing import TypeVar, Optional + +from api.datatype.Eatery import Eatery +from api.datatype.Event import Event +from api.datatype.WaitTimesByDay import WaitTimesByDay +from api.dfg.DfgNode import DfgNode +from api.dfg.EateryToJson import EateryToJson + +T = TypeVar('T') + +class LeftMerge(DfgNode): + + def __init__(self, left: DfgNode, right: DfgNode): + self.left = left + self.right = right + + def __call__(self, *args, **kwargs): + merged_eateries = [] + right_eateries = self.right(*args, **kwargs) + for left_eatery in self.left(*args, **kwargs): + updated_eatery = next((right_eatery for right_eatery in right_eateries if right_eatery.id == left_eatery.id), None) + if updated_eatery == None: + merged_eateries.append(left_eatery) + else: + right_eateries = [right_eatery for right_eatery in right_eateries if right_eatery.id != left_eatery.id] + + merged_eateries.add(right_eateries) + return merged_eateries + + @staticmethod + def merge_eateries(left: Eatery, right: Eatery): + merged_events = LeftMerge.merge_events(left.events, right.events) + return Eatery( + id=left.id, + name=LeftMerge.merge_fields(left.name, right.name), + campus_area=LeftMerge.merge_fields(left.campus_area, right.campus_area), + events=merged_events, + latitude=LeftMerge.merge_fields(left.latitude, right.latitude), + longitude=LeftMerge.merge_fields(left.longitude, right.longitude), + location=LeftMerge.merge_fields(left.location, right.location), + online_order=LeftMerge.merge_fields(left.online_order, right.online_order), + online_order_url=LeftMerge.merge_fields(left.online_order_url, right.online_order_url), + wait_times=LeftMerge.merge_and_filter_waittimes(left.wait_times, right.wait_times, merged_events) + ) + + @staticmethod + def merge_fields(left: Optional[T], right: Optional[T]) -> Optional[T]: + return right if left is None else left + + @staticmethod + def merge_events(left: list[Event], right: list[Event]): + merged_events = [] + for left_event in left: + right = [ + right_event for right_event in right if + right_event.canonical_date != left_event.canonical_date or + right_event.description != left_event.description + ] + merged_events.add(left_event) + merged_events.add(right) + return merged_events + + @staticmethod + def merge_and_filter_waittimes(left: list[WaitTimesByDay], right: list[WaitTimesByDay], events: list[Event]) -> list[WaitTimesByDay]: + wait_times_filtered = [] + wait_times = LeftMerge.merge_fields(left, right) + if wait_times is None: + return None + for wait_times_by_day in wait_times: + wait_times_by_day_filtered = [] + for wait_time in wait_times_by_day.daily_wait_times: + if any([wait_time.timestamp in event for event in events]): + wait_times_by_day_filtered.append(wait_time) + wait_times_filtered.append( + WaitTimesByDay(canonical_date=wait_times_by_day.canonical_date, wait_times=wait_times_by_day_filtered) + ) + return wait_times_filtered + + + def children(self): + return self.children + + def description(self): + return "Merge" \ No newline at end of file diff --git a/api/dfg/WaitTimes.py b/api/dfg/WaitTimes.py new file mode 100644 index 0000000..441b839 --- /dev/null +++ b/api/dfg/WaitTimes.py @@ -0,0 +1,136 @@ +from typing import Mapping +from django.db.models import Avg +from datetime import date, datetime, timedelta +import pytz + +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.WaitTimesByDay import WaitTimesByDay +from api.datatype.WaitTime import WaitTime + +from api.dfg.DfgNode import DfgNode +from transactions.models import TransactionHistory +from transactions.serializers import TransactionHistorySerializer + +class WaitTimes(DfgNode): + + def __call__(self, *args, **kwargs) -> list[Eatery]: + transactions_by_date = {} + past_days = [] + date = kwargs.get("start") + eatery_ids = set() + while date <= kwargs.get("end"): + # We only calculate the wait times for this first day + transactions_on_date = {} + for i in range(1, 13): + # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the transaction_count + past_day = date - timedelta(days = 7 * i) + past_days.append(past_day) + transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ + .values("id", "block_end_time") \ + .annotate(transaction_avg=Avg("transaction_count")) + for unit in transaction_avg_counts: + transaction_history = TransactionHistorySerializer(unit) + eatery_id = EateryID(transaction_history.data['id']) + eatery_ids.add(eatery_id) + if eatery_id not in transactions_on_date: + transactions_on_date[eatery_id] = [] + transactions_on_date[eatery_id].append(transaction_history) + transactions_by_date[date] = transactions_on_date + date += timedelta(days=1) + + eateries = [] + for eatery_id in eatery_ids: + eatery_wait_times_by_day = [] + for date in transactions_by_date: + if eatery_id in transactions_by_date[date]: + eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day(eatery_id)) + eateries.append( + Eatery( + eatery_id=eatery_id, + wait_times = eatery_wait_times_by_day + ) + ) + return eateries + + # Expected amount of time (in seconds) for the length of the line to decrease by 1 person + # Returns [lower, expected, upper] + @staticmethod + def line_decrease_by_one_time(eatery_id: EateryID) -> float: + if eatery_id == EateryID.MACS_CAFE: + return [24, 27, 30] + elif eatery_id == EateryID.MATTINS_CAFE: + return [9, 15, 21] + elif eatery_id == EateryID.TERRACE: + return [15, 27, 36] + elif eatery_id == EateryID.OKENSHIELDS: + return [4, 8, 12] + else: + return [18, 21, 24] + + # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the amount of time to check out + # Returns [lower, expected, upper] + @staticmethod + def base_time_to_get_food(eatery_id: int) -> float: + if eatery_id == EateryID.MACS_CAFE: + return [240, 300, 360] + elif eatery_id == EateryID.MATTINS_CAFE: + return [150, 210, 270] + elif eatery_id == EateryID.TERRACE: + return [180, 300, 420] + elif eatery_id == EateryID.OKENSHIELDS: + return [80, 120, 180] + else: + return [180, 240, 300] + + @staticmethod + def generate_eatery_wait_times_by_day( + eatery_id: int, + date: date, + transactions: list[TransactionHistorySerializer] + ) -> WaitTimesByDay: + wait_times = [] + customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + for index in reversed(range(0, len(transactions))): + base_times = WaitTimes.base_time_to_get_food(eatery_id) + line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) + # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago + how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] + prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) + if prev_bucket_guest_arrival > 9: + # TODO: Send a slack error here instead + print(how_long_ago_guest_arrival) + print(date) + print(transactions[index].data) + print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") + else: + customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index].data["transaction_avg"] + num_customers = customers_waiting_in_line.pop(0) + wait_time_low = int(base_times[0] + line_decrease_times[0] * num_customers) + wait_time_expected = int(base_times[1] + line_decrease_times[1] * num_customers) + wait_time_high = int(base_times[2] + line_decrease_times[2] * num_customers) + + customers_waiting_in_line.append(0.0) + block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() + timestamp = int(WaitTimes.timestamp_combined(date, block_end_time) - 5 * 60 / 2) + wait_times.insert(0, WaitTime( + timestamp=timestamp, + wait_time_low = wait_time_low, + wait_time_expected= wait_time_expected, + wait_time_high = wait_time_high)) + + return WaitTimesByDay(date, wait_times) + + @staticmethod + def timestamp_combined(date: datetime.date, time: datetime.time): + """ + Returns the Unix (UTC) timestamp of the combined (date, time) in the + New York timezone. + """ + + tz = pytz.timezone('America/New_York') + return int(tz.localize(datetime.combine(date, time)).timestamp()) + + + + def description(self): + return "FetchTransactionCounts" diff --git a/api/dfg/assembly/datatype/Eatery.py b/api/dfg/assembly/datatype/Eatery.py deleted file mode 100644 index 1cfe371..0000000 --- a/api/dfg/assembly/datatype/Eatery.py +++ /dev/null @@ -1,61 +0,0 @@ -from datetime import date -from typing import Optional - -import pytz - -from api.dfg.preparation.datatype.Event import Event, filter_range - -class Eatery: - - def __init__( - self, - id: int, - name: str, - campus_area: str, - events: list[Event], - latitude: float, - longitude: float, - payment_methods: list[str], - location: str, - online_order: bool, - online_order_url: str - ): - self.id = id - self.name = name - self.campus_area = campus_area - self.latitude = latitude - self.longitude = longitude - self.known_events = events - self.payment_methods = payment_methods - self.location = location - self.online_order = online_order - self.online_order_url = online_order_url - - def events( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None, - ): - return filter_range(self.known_events, tzinfo, start, end) - - def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None - ): - eatery = { - "id": self.id, - "name": self.name, - "campus_area": self.campus_area, - "events": [event.to_json() for event in self.events(tzinfo, start, end)], - "latitude": self.latitude, - "longitude": self.longitude, - "payment_methods": None if self.payment_methods is None else [payment_method for payment_method in self.payment_methods], - "location": self.location, - "online_order": self.online_order, - "online_order_url": self.online_order_url - } - return eatery - diff --git a/api/dfg/preparation/EateryStubs.py b/api/dfg/preparation/EateryStubs.py deleted file mode 100644 index d3d4639..0000000 --- a/api/dfg/preparation/EateryStubs.py +++ /dev/null @@ -1,26 +0,0 @@ - -from api.dfg.DfgNode import DfgNode -from api.dfg.preparation.datatype.EateryStub import EateryStub - -import json - -class EateryStubs(DfgNode): - ALL_EATERIES_PATH = "static_sources/cornell_eateries.json" - - def __call__(self, *args, **kwargs) -> list[EateryStub]: - eateries = [] - - with open(EateryStubs.ALL_EATERIES_PATH) as f: - json_eateries = json.load(f)["eateries"] - - for json_eatery in json_eateries: - eateries.append(EateryStubs.eatery_from_json(json_eatery)) - - return eateries - - @staticmethod - def eatery_from_json(json_eatery: dict) -> EateryStub: - return EateryStub( - name = json_eatery["name"], - id = json_eatery["id"] - ) diff --git a/api/dfg/preparation/datatype/CornellDiningEatery.py b/api/dfg/preparation/datatype/CornellDiningEatery.py deleted file mode 100644 index 4cda151..0000000 --- a/api/dfg/preparation/datatype/CornellDiningEatery.py +++ /dev/null @@ -1,64 +0,0 @@ -from datetime import date -from typing import Optional - -import pytz - -from api.dfg.preparation.datatype.Event import Event, filter_range - -class CornellDiningEatery: - - def __init__( - self, - name: str, - about: str, - about_short: str, - campus_area: str, - events: list[Event], - latitude: float, - longitude: float, - payment_methods: list[str], - location: str, - online_order: bool, - online_order_url: str - ): - self.name = name - self.about = about - self.about_short = about_short - self.campus_area = campus_area - self.latitude = latitude - self.longitude = longitude - self.known_events = events - self.payment_methods = payment_methods - self.location = location - self.online_order = online_order - self.online_order_url = online_order_url - - def events( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None, - ): - return filter_range(self.known_events, tzinfo, start, end) - - def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None - ): - eatery = { - "name": self.name, - "about": self.about, - "about_short": self.about_short, - "campus_area": self.campus_area, - "events": [event.to_json() for event in self.events(tzinfo, start, end)], - "latitude": self.latitude, - "longitude": self.longitude, - "payment_methods": [payment_method for payment_method in self.payment_methods], - "location": self.location, - "online_order": self.online_order, - "online_order_url": self.online_order_url - } - return eatery - diff --git a/api/dfg/preparation/datatype/EateryStub.py b/api/dfg/preparation/datatype/EateryStub.py deleted file mode 100644 index 90aeac6..0000000 --- a/api/dfg/preparation/datatype/EateryStub.py +++ /dev/null @@ -1,5 +0,0 @@ - -class EateryStub: - def __init__(self, name: str, id: int): - self.name = name - self.id = id diff --git a/api/dfg/preparation/datatype/OverrideEatery.py b/api/dfg/preparation/datatype/OverrideEatery.py deleted file mode 100644 index 0fa58a4..0000000 --- a/api/dfg/preparation/datatype/OverrideEatery.py +++ /dev/null @@ -1,61 +0,0 @@ -from datetime import date -from typing import Optional - -import pytz - -from api.dfg.preparation.datatype.OverrideEvent import OverrideEvent, filter_range - -class OverrideEatery: - - def __init__( - self, - name: str, - about: Optional[str] = None, - campus_area: Optional[str] = None, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - events: Optional[list[OverrideEvent]] = None, - payment_methods: Optional[list[str]] = None, - location: Optional[str] = None, - online_order: Optional[bool] = None, - online_order_url: Optional[str] = None - ): - self.name = name - self.about = about - self.campus_area = campus_area - self.latitude = latitude - self.longitude = longitude - self.known_events = events - self.payment_methods = payment_methods - self.location = location - self.online_order = online_order - self.online_order_url = online_order_url - - def events( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None, - ): - return filter_range(self.known_events, tzinfo, start, end) - - def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None - ): - eatery = { - "name": self.name, - "about": self.about, - "campus_area": self.campus_area, - "events": [event.to_json() for event in self.events(tzinfo, start, end)], - "latitude": self.latitude, - "longitude": self.longitude, - "payment_methods": [payment_method for payment_method in self.payment_methods], - "location": self.location, - "online_order": self.online_order, - "online_order_url": self.online_order_url - } - return eatery - diff --git a/api/dfg/preparation/datatype/OverrideEvent.py b/api/dfg/preparation/datatype/OverrideEvent.py deleted file mode 100644 index bc3e877..0000000 --- a/api/dfg/preparation/datatype/OverrideEvent.py +++ /dev/null @@ -1,63 +0,0 @@ -from typing import Optional - -from datetime import date, datetime, time - -from api.dfg.preparation.datatype.Menu import Menu - -import pytz - -class OverrideEvent: - - # Pass in [exists = false] to remove events with the same description and canonical_date from occuring - def __init__( - self, - description: str, - canonical_date: date, - start_timestamp: int, - end_timestamp: int, - menu: Menu, - exists: bool - ): - self.description = description - self.canonical_date = canonical_date - self.start_timestamp = start_timestamp - self.end_timestamp = end_timestamp - self.menu = menu - self.exists = exists - - def to_json(self): - return { - "description": self.description, - "canonical_date": str(self.canonical_date), - "start_timestamp": self.start_timestamp, - "end_timestamp": self.end_timestamp, - "menu": self.menu.to_json() - } - - def __contains__(self, item: int): - return self.start_timestamp <= item <= self.end_timestamp - - -def _combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: - return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) - - -def filter_range(events: list[OverrideEvent], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): - if start is None and end is None: - return events - - elif tzinfo is not None and start is not None and end is None: - start_ts = _combined_timestamp(start, time(), tzinfo) - return [event for event in events if ( - (start_ts in event) or start == event.canonical_date - )] - - elif tzinfo is not None and start is not None and end is not None: - start_ts = _combined_timestamp(start, time(), tzinfo) - end_ts = _combined_timestamp(end, time(), tzinfo) - return [event for event in events if ( - (start_ts in event) or (end_ts in event) or start <= event.canonical_date <= end - )] - - else: - raise Exception(f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}") diff --git a/api/dfg/waittimes/AddWaitTimesToEateries.py b/api/dfg/waittimes/AddWaitTimesToEateries.py deleted file mode 100644 index b78d129..0000000 --- a/api/dfg/waittimes/AddWaitTimesToEateries.py +++ /dev/null @@ -1,125 +0,0 @@ -# takes in api_data, google_sheets data, and stubs, and generates a list[Eatery] - -from typing import Mapping -from datetime import date, datetime -import pytz - -from api.dfg.DfgNode import DfgNode -from api.dfg.assembly.datatype.Eatery import Eatery -from api.dfg.DfgNode import DfgNode -from api.dfg.waittimes.datatype.EateryWithWaitTimes import EateryWithWaitTimes -from api.dfg.waittimes.datatype.WaitTime import WaitTime -from api.dfg.waittimes.datatype.WaitTimesByDay import WaitTimesByDay - -from transactions.serializers import TransactionHistorySerializer - - -class AddWaitTimesToEateries(DfgNode): - - def __init__(self, eateries: DfgNode, transaction_counts: DfgNode): - self.eateries = eateries - self.transaction_counts = transaction_counts - - def __call__(self, *args, **kwargs): - results = [] - transactions_by_date = self.transaction_counts(*args, **kwargs) - - for eatery in self.eateries(*args, **kwargs): - results.append(AddWaitTimesToEateries.generate_eatery_result(eatery, transactions_by_date)) - - return results - - - # Expected amount of time (in seconds) for the length of the line to decrease by 1 person - # Returns [lower, expected, upper] - @staticmethod - def LINE_DECREASE_BY_ONE_TIME(eatery_name: str) -> float: - # TODO: Move these hardcoded names into a string file - if eatery_name == "Mac's Café": - return [24, 27, 30] - elif eatery_name == "Mattin's Café": - return [9, 15, 21] - elif eatery_name == "Terrace Restaurant": - return [15, 27, 36] - elif eatery_name == "Okenshields": - return [4, 8, 12] - else: - return [18, 21, 24] - - # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the amount of time to check out - # Returns [lower, expected, upper] - @staticmethod - def BASE_TIME_TO_GET_FOOD(eatery_name: str) -> float: - if eatery_name == "Mac's Café": - return [240, 300, 360] - elif eatery_name == "Mattin's Café": - return [150, 210, 270] - elif eatery_name == "Terrace Restaurant": - return [180, 300, 420] - elif eatery_name == "Okenshields": - return [80, 120, 180] - else: - return [180, 240, 300] - - @staticmethod - def generate_eatery_wait_times_by_day( - eatery: Eatery, - date: date, - transactions: list[TransactionHistorySerializer] - ) -> WaitTimesByDay: - wait_times = [] - customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - for index in reversed(range(0, len(transactions))): - base_times = AddWaitTimesToEateries.BASE_TIME_TO_GET_FOOD(eatery.name) - line_decrease_times = AddWaitTimesToEateries.LINE_DECREASE_BY_ONE_TIME(eatery.name) - # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago - how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] - prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) - if prev_bucket_guest_arrival > 9: - # TODO: Send a slack error here instead - print(how_long_ago_guest_arrival) - print(date) - print(transactions[index].data) - print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") - else: - customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index].data["transaction_avg"] - num_customers = customers_waiting_in_line.pop(0) - wait_time_low = int(base_times[0] + line_decrease_times[0] * num_customers) - wait_time_expected = int(base_times[1] + line_decrease_times[1] * num_customers) - wait_time_high = int(base_times[2] + line_decrease_times[2] * num_customers) - - customers_waiting_in_line.append(0.0) - block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() - timestamp = int(AddWaitTimesToEateries.timestamp_combined(date, block_end_time) - 5 * 60 / 2) - if any([timestamp in event for event in eatery.events()]): - wait_times.insert(0, WaitTime( - timestamp=timestamp, - wait_time_low = wait_time_low, - wait_time_expected= wait_time_expected, - wait_time_high = wait_time_high)) - - return WaitTimesByDay(date, wait_times) - - @staticmethod - def generate_eatery_result( - eatery: Eatery, - transactions_by_date: Mapping[date, Mapping[str, list[TransactionHistorySerializer]]] - ) -> EateryWithWaitTimes: - eatery_wait_times_by_day = [] - for date in transactions_by_date: - if eatery.name in transactions_by_date[date]: - eatery_wait_times_by_day.append(AddWaitTimesToEateries.generate_eatery_wait_times_by_day(eatery, date, transactions_by_date[date][eatery.name])) - return EateryWithWaitTimes(eatery, eatery_wait_times_by_day) - - @staticmethod - def timestamp_combined(date: datetime.date, time: datetime.time): - """ - Returns the Unix (UTC) timestamp of the combined (date, time) in the - New York timezone. - """ - - tz = pytz.timezone('America/New_York') - return int(tz.localize(datetime.combine(date, time)).timestamp()) - - def description(self): - return "AddWaitTimesToEateries" diff --git a/api/dfg/waittimes/FetchTransactionCounts.py b/api/dfg/waittimes/FetchTransactionCounts.py deleted file mode 100644 index becc04a..0000000 --- a/api/dfg/waittimes/FetchTransactionCounts.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Mapping -from django.db.models import Avg -from datetime import date, timedelta - -from api.dfg.DfgNode import DfgNode -from transactions.models import TransactionHistory -from transactions.serializers import TransactionHistorySerializer - -class FetchTransactionCounts(DfgNode): - - def __call__(self, *args, **kwargs) -> Mapping[date, Mapping[str, list[TransactionHistory]]]: - transactions_by_date = {} - past_days = [] - date = kwargs.get("start") - while date <= kwargs.get("end"): - # We only calculate the wait times for this first day - transactions_on_date = {} - for i in range(1, 13): - # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the transaction_count - past_day = date - timedelta(days = 7 * i) - past_days.append(past_day) - transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ - .values("name", "block_end_time") \ - .annotate(transaction_avg=Avg("transaction_count")) - for unit in transaction_avg_counts: - transaction_history = TransactionHistorySerializer(unit) - eatery_name = transaction_history.data['name'] - if eatery_name not in transactions_on_date: - transactions_on_date[eatery_name] = [] - transactions_on_date[eatery_name].append(transaction_history) - transactions_by_date[date] = transactions_on_date - date += timedelta(days=1) - - return transactions_by_date - - def description(self): - return "FetchTransactionCounts" diff --git a/api/dfg/waittimes/datatype/EateryWithWaitTimes.py b/api/dfg/waittimes/datatype/EateryWithWaitTimes.py deleted file mode 100644 index 16d0bc3..0000000 --- a/api/dfg/waittimes/datatype/EateryWithWaitTimes.py +++ /dev/null @@ -1,28 +0,0 @@ -# Eatery type returned to the frontend - -from datetime import date -from typing import Mapping, Optional -from ...assembly.datatype.Eatery import Eatery -from .WaitTimesByDay import WaitTimesByDay - -import pytz - -class EateryWithWaitTimes: - - def __init__( - self, - eatery: Eatery, - wait_times_by_day: list[WaitTimesByDay] - ): - self.eatery = eatery - self.wait_times_by_day = wait_times_by_day - - def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None - ): - eatery_json = self.eatery.to_json(tzinfo=tzinfo, start=start, end=end) - eatery_json["wait_times_by_day"] = [day_wait_times.to_json() for day_wait_times in self.wait_times_by_day] - return eatery_json diff --git a/api/utils.py b/api/utils.py new file mode 100644 index 0000000..4b525b1 --- /dev/null +++ b/api/utils.py @@ -0,0 +1,4 @@ + + + +def EATERY_ID_TO_NAME(): diff --git a/api/views.py b/api/views.py index cb0c592..e87ec3f 100644 --- a/api/views.py +++ b/api/views.py @@ -2,14 +2,14 @@ import pytz from django.http import JsonResponse -from api.dfg.waittimes.FetchTransactionCounts import FetchTransactionCounts +from api.dfg.WaitTimes import FetchTransactionCounts -from api.dfg.preparation.CornellDiningNow import CornellDiningNow -from api.dfg.preparation.EateryStubs import EateryStubs -from api.dfg.preparation.ExternalEateries import ExternalEateries +from api.dfg.CornellDiningNow import CornellDiningNow +from api.dfg.EateryStubs import EateryStubs +from api.dfg.ExternalEateries import ExternalEateries -from api.dfg.assembly.AssembleEateries import AssembleEateries -from api.dfg.waittimes.AddWaitTimesToEateries import AddWaitTimesToEateries +from api.dfg.AssembleEateries import AssembleEateries +from api.dfg.AddWaitTimesToEateries import AddWaitTimesToEateries from api.dfg.DictResponseWrapper import DictResponseWrapper from api.dfg.EateryToJson import EateryToJson diff --git a/eateries/__init__.py b/eateries/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/eateries/admin.py b/eateries/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/eateries/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/eateries/apps.py b/eateries/apps.py new file mode 100644 index 0000000..6a490f8 --- /dev/null +++ b/eateries/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EateriesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'eateries' diff --git a/eateries/migrations/__init__.py b/eateries/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/eateries/models.py b/eateries/models.py new file mode 100644 index 0000000..af5f150 --- /dev/null +++ b/eateries/models.py @@ -0,0 +1,11 @@ +from django.db import models + +# Create your models here. +class Eatery(models.Model): + class Meta: + unique_together = ('name', 'block_end_time', 'canonical_date') + indexes = [models.Index(fields = ['canonical_date'])] + ud + canonical_date = models.DateField() + block_end_time = models.TimeField() + transaction_count = models.IntegerField() \ No newline at end of file diff --git a/eateries/tests.py b/eateries/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/eateries/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/eateries/views.py b/eateries/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/eateries/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/eatery_blue_backend/settings.py b/eatery_blue_backend/settings.py index cb312ce..113c456 100644 --- a/eatery_blue_backend/settings.py +++ b/eatery_blue_backend/settings.py @@ -37,7 +37,8 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "transactions" + "transactions", + "eateries" ] MIDDLEWARE = [ diff --git a/static_sources/cornell_eateries.json b/static_sources/cornell_eateries.json deleted file mode 100644 index ad2c94f..0000000 --- a/static_sources/cornell_eateries.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "eateries": [ - { - "id": 1, - "name": "104West!" - }, - { - "id": 2, - "name": "Amit Bhatia Libe Café" - }, - { - "id": 3, - "name": "Atrium Café" - }, - { - "id": 4, - "name": "Bear Necessities Grill & C-Store" - }, - { - "id": 5, - "name": "Becker House Dining Room" - }, - { - "id": 6, - "name": "Big Red Barn" - }, - { - "id": 7, - "name": "Bus Stop Bagels" - }, - { - "id": 8, - "name": "Café Jennie" - }, - { - "id": 9, - "name": "Carol's Café" - }, - { - "id": 10, - "name": "Cook House Dining Room" - }, - { - "id": 11, - "name": "Cornell Dairy Bar" - }, - { - "id": 12, - "name": "Crossings Café" - }, - { - "id": 13, - "name": "Franny's" - }, - { - "id": 14, - "name": "Goldie's Café" - }, - { - "id": 15, - "name": "Green Dragon" - }, - { - "id": 16, - "name": "Hot Dog Cart" - }, - { - "id": 17, - "name": "Ice Cream Bike" - }, - { - "id": 18, - "name": "Jansen's Dining Room at Bethe House" - }, - { - "id": 19, - "name": "Jansen's Market" - }, - { - "id": 20, - "name": "Keeton House Dining Room" - }, - { - "id": 21, - "name": "Mann Café" - }, - { - "id": 22, - "name": "Martha's Café" - }, - { - "id": 23, - "name": "Mattin's Café" - }, - { - "id": 24, - "name": "McCormick's at Moakley House" - }, - { - "id": 25, - "name": "North Star Dining Room" - }, - { - "id": 26, - "name": "Okenshields" - }, - { - "id": 27, - "name": "Risley Dining Room" - }, - { - "id": 28, - "name": "Robert Purcell Marketplace Eatery" - }, - { - "id": 29, - "name": "Rose House Dining Room" - }, - { - "id": 30, - "name": "Rusty's" - }, - { - "id": 31, - "name": "Straight from the Market" - }, - { - "id": 32, - "name": "Trillium" - }, - { - "id": 33, - "name": "Terrace Restaurant" - }, - { - "id": 34, - "name": "Mac's Café" - }, - { - "id": 35, - "name": "Temple of Zeus" - }, - { - "id": 36, - "name": "Gimme Coffee" - }, - { - "id": 37, - "name": "Louie's Lunch" - }, - { - "id": 38, - "name": "Anabel's Grocery" - } - ] -} \ No newline at end of file diff --git a/static_sources/external_eateries.json b/static_sources/external_eateries.json index 889ba80..a2cf74f 100644 --- a/static_sources/external_eateries.json +++ b/static_sources/external_eateries.json @@ -1,6 +1,7 @@ { "eateries": [ { + "id": 33, "slug": "Terrace", "external": true, "name": "Terrace Restaurant", @@ -93,6 +94,7 @@ ] }, { + "id": 34, "slug": "Macs", "external": true, "name": "Mac's Café", @@ -194,6 +196,7 @@ ] }, { + "id": 35, "slug": "Zeus", "external": true, "name": "Temple of Zeus", @@ -273,6 +276,7 @@ ] }, { + "id": 36, "slug": "Gimme-Coffee", "external": true, "name": "Gimme Coffee", @@ -335,6 +339,7 @@ ] }, { + "id": 37, "slug": "Louies-Lunch", "external": true, "name": "Louie's Lunch", @@ -464,6 +469,7 @@ ] }, { + "id": 38, "slug": "Anabels-Grocery", "external": true, "name": "Anabel's Grocery", diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index bc8f57d..9e165bd 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -10,75 +10,75 @@ class UpdateTransactionsController: # Converts the Vendor's name for the eatery into the name stored in our backend @staticmethod - def eatery_name(vendor_eatery_name): + def vendor_name_to_internal_id(vendor_eatery_name): vendor_eatery_name = ''.join(c.lower() for c in vendor_eatery_name if c.isalpha()) if vendor_eatery_name == "bearnecessities": - return "Bear Necessities Grill & C-Store" + return 4 elif vendor_eatery_name == "northstarmarketplace": - return "North Star Dining Room" + return 25 elif vendor_eatery_name == "jansensmarket": - return "Jansen's Market" + return 19 elif vendor_eatery_name == "stockinghallcafe" or vendor_eatery_name == "stockinghall": - return "Cornell Dairy Bar" + return 11 elif vendor_eatery_name == "marthas": - return "Martha's Café" + return 22 elif vendor_eatery_name == "cafejennie": - return "Café Jennie" + return 8 elif vendor_eatery_name == "goldiescafe": - return "Goldie's Café" + return 14 elif vendor_eatery_name == "alicecookhouse": - return "Cook House Dining Room" + return 10 elif vendor_eatery_name == "carlbeckerhouse": - return "Becker House Dining Room" + return 5 elif vendor_eatery_name == "duffield": - return "Mattin's Café" + return 23 elif vendor_eatery_name == "greendragon": - return "Green Dragon" + return 15 elif vendor_eatery_name == "trillium": - return "Trillium" + return 32 elif vendor_eatery_name == "olinlibecafe": - return "Amit Bhatia Libe Café" + return 2 elif vendor_eatery_name == "carolscafe": - return "Carol's Café" + return 9 elif vendor_eatery_name == "statlerterrace": - return "Terrace Restaurant" + return 33 elif vendor_eatery_name == "busstopbagels": - return "Bus Stop Bagels" + return 7 elif vendor_eatery_name == "kosher": - return "104West!" + return 1 elif vendor_eatery_name == "jansensatbethehouse": - return "Jansen's Dining Room at Bethe House" + return 18 elif vendor_eatery_name == "keetonhouse": - return "Keeton House Dining Room" + return 20 elif vendor_eatery_name == "rpme": - return "Robert Purcell Marketplace Eatery" + return 28 elif vendor_eatery_name == "rosehouse": - return "Rose House Dining Room" + return 29 elif vendor_eatery_name == "risley": - return "Risley Dining Room" + return 27 elif vendor_eatery_name == "frannysft": - return "Franny's" + return 13 elif vendor_eatery_name == "mccormicks": - return "McCormick's at Moakley House" + return 24 elif vendor_eatery_name == "sage": - return "Atrium Café" + return 3 elif vendor_eatery_name == "straightmarket": - return "Straight from the Market" + return 31 elif vendor_eatery_name == "crossingscafe": - return "Crossings Café" + return 12 elif vendor_eatery_name == "okenshields": - return "Okenshields" + return 26 elif vendor_eatery_name == "bigredbarn": - return "Big Red Barn" + return 6 elif vendor_eatery_name == "rustys": - return "Rusty's" + return 30 elif vendor_eatery_name == "manncafe": - return "Mann Café" + return 21 elif vendor_eatery_name == "statlermacs": - return "Mac's Café" + return 34 else: # TODO: Add a slack notif / flag that a wait time location was not recognized - return "" + return -1 def __init__(self, data): self._data = data @@ -106,13 +106,13 @@ def process(self): num_inserted = 0 ignored_names = set() for place in self._data["UNITS"]: - name = UpdateTransactionsController.eatery_name(place["UNIT_NAME"]) - if len(name) == 0: + internal_id = UpdateTransactionsController.vendor_name_to_internal_id(place["UNIT_NAME"]) + if id < 0: ignored_names.add(place["UNIT_NAME"]) else: num_inserted += 1 try: - TransactionHistory.objects.create(name = name, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) + TransactionHistory.objects.create(id = internal_id, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) except: num_inserted -= 1 return { diff --git a/transactions/models.py b/transactions/models.py index 5811d26..78bdfe4 100644 --- a/transactions/models.py +++ b/transactions/models.py @@ -5,9 +5,9 @@ # [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] class TransactionHistory(models.Model): class Meta: - unique_together = ('name', 'block_end_time', 'canonical_date') + unique_together = ('id', 'block_end_time', 'canonical_date') indexes = [models.Index(fields = ['canonical_date'])] - name = models.CharField(max_length=100) + id = models.IntegerField() canonical_date = models.DateField() block_end_time = models.TimeField() transaction_count = models.IntegerField() \ No newline at end of file From d306792934caba0b92d19f811ebb7ee7035594a5 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 3 Jan 2022 18:42:51 -0500 Subject: [PATCH 023/305] Finalize transition to left merge dfg system --- api/datatype/Eatery.py | 80 ++++++------- api/datatype/Event.py | 5 +- .../{WaitTimesByDay.py => WaitTimesDay.py} | 8 +- api/dfg/AssembleEateries.py | 107 ------------------ api/dfg/CornellDiningNow.py | 8 +- api/dfg/EateryStubs.py | 2 +- api/dfg/EateryToJson.py | 7 +- api/dfg/LeftMerge.py | 27 +++-- api/dfg/WaitTimes.py | 22 ++-- api/utils.py | 4 - api/views.py | 25 ++-- eateries/models.py | 18 +-- eatery_blue_backend/settings.py | 2 +- .../update_transactions_controller.py | 74 ++++++------ ...sactionhistory_unique_together_and_more.py | 31 +++++ transactions/models.py | 4 +- transactions/serializers.py | 2 +- 17 files changed, 177 insertions(+), 249 deletions(-) rename api/datatype/{WaitTimesByDay.py => WaitTimesDay.py} (55%) delete mode 100644 api/dfg/AssembleEateries.py delete mode 100644 api/utils.py create mode 100644 transactions/migrations/0009_alter_transactionhistory_unique_together_and_more.py diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index b0fa8bb..fcc0f9c 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -5,47 +5,47 @@ import pytz from api.datatype.Event import Event, filter_range -from api.datatype.WaitTimesByDay import WaitTimesByDay +from api.datatype.WaitTimesDay import WaitTimesDay class EateryID(Enum): - NULL = 0, - ONE_ZERO_FOUR_WEST = 1, - LIBE_CAFE = 2, - ATRIUM_CAFE = 3, - BEAR_NECESSITIES = 4, - BECKER_HOUSE = 5, - BIG_RED_BARN = 6, - BUS_STOP_BAGELS = 7, - CAFE_JENNIE = 8, - CAROLS_CAFE = 9, - COOK_HOUSE = 10, - DAIRY_BAR = 11, + NULL = 0 + ONE_ZERO_FOUR_WEST = 1 + LIBE_CAFE = 2 + ATRIUM_CAFE = 3 + BEAR_NECESSITIES = 4 + BECKER_HOUSE = 5 + BIG_RED_BARN = 6 + BUS_STOP_BAGELS = 7 + CAFE_JENNIE = 8 + CAROLS_CAFE = 9 + COOK_HOUSE = 10 + DAIRY_BAR = 11 CROSSINGS_CAFE = 12 - FRANNYS = 13, - GOLDIES_CAFE = 14, - GREEN_DRAGON = 15, - HOT_DOG_CART = 16, - ICE_CREAM_BIKE = 17, - BETHE_HOUSE = 18, - JANSENS_MARKET = 19, - KEETON_HOUSE = 20, - MANN_CAFE = 21, - MARTHAS_CAFE = 22, - MATTINS_CAFE = 23, - MCCORMICKS = 24, - NORTH_STAR_DINING = 25, - OKENSHIELDS = 26, - RISLEY = 27, - RPCC = 28, - ROSE_HOUSE = 29, - RUSTYS = 30, - STRAIGHT_FROM_THE_MARKET = 31, - TRILLIUM = 32, - TERRACE = 33, - MACS_CAFE = 34, - TEMPLE_OF_ZEUS = 35, - GIMME_COFFEE = 36, - LOUIES = 37, + FRANNYS = 13 + GOLDIES_CAFE = 14 + GREEN_DRAGON = 15 + HOT_DOG_CART = 16 + ICE_CREAM_BIKE = 17 + BETHE_HOUSE = 18 + JANSENS_MARKET = 19 + KEETON_HOUSE = 20 + MANN_CAFE = 21 + MARTHAS_CAFE = 22 + MATTINS_CAFE = 23 + MCCORMICKS = 24 + NORTH_STAR_DINING = 25 + OKENSHIELDS = 26 + RISLEY = 27 + RPCC = 28 + ROSE_HOUSE = 29 + RUSTYS = 30 + STRAIGHT_FROM_THE_MARKET = 31 + TRILLIUM = 32 + TERRACE = 33 + MACS_CAFE = 34 + TEMPLE_OF_ZEUS = 35 + GIMME_COFFEE = 36 + LOUIES = 37 ANABELS_GROCERY = 38 class Eatery: @@ -62,7 +62,7 @@ def __init__( location: Optional[str] = None, online_order: Optional[bool] = None, online_order_url: Optional[str] = None, - wait_times: Optional[list[WaitTimesByDay]] = None + wait_times: Optional[list[WaitTimesDay]] = None ): self.id = id self.name = name @@ -101,6 +101,6 @@ def to_json( "location": self.location, "online_order": self.online_order, "online_order_url": self.online_order_url, - "wait_times": [wait_time.to_json() for wait_time in self.wait_times] + "wait_times": [] if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times] } return eatery diff --git a/api/datatype/Event.py b/api/datatype/Event.py index 4125a6d..a0ea762 100644 --- a/api/datatype/Event.py +++ b/api/datatype/Event.py @@ -21,7 +21,7 @@ def __init__( self.canonical_date = canonical_date self.start_timestamp = start_timestamp self.end_timestamp = end_timestamp - self.menu = menu, + self.menu = menu self.exists = exists def to_json(self): @@ -42,6 +42,9 @@ def _combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): + if events is None: + return [] + if start is None and end is None: return events diff --git a/api/datatype/WaitTimesByDay.py b/api/datatype/WaitTimesDay.py similarity index 55% rename from api/datatype/WaitTimesByDay.py rename to api/datatype/WaitTimesDay.py index 6356a35..f4d1d32 100644 --- a/api/datatype/WaitTimesByDay.py +++ b/api/datatype/WaitTimesDay.py @@ -2,18 +2,18 @@ from .WaitTime import WaitTime -class WaitTimesByDay: +class WaitTimesDay: def __init__( self, canonical_date: str, - daily_wait_times: list[WaitTime] + data: list[WaitTime] ): self.canonical_date = canonical_date - self.daily_wait_times = daily_wait_times + self.data = data def to_json(self): return { "canonical_date": self.canonical_date, - "daily_wait_times": [wait_time.to_json() for wait_time in self.daily_wait_times] + "data": [wait_time.to_json() for wait_time in self.data] } diff --git a/api/dfg/AssembleEateries.py b/api/dfg/AssembleEateries.py deleted file mode 100644 index ba6d6a7..0000000 --- a/api/dfg/AssembleEateries.py +++ /dev/null @@ -1,107 +0,0 @@ -# takes in api_data, google_sheets data, and stubs, and generates a list[Eatery] - -from typing import Optional, TypeVar -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery -from api.dfg.preparation.datatype.EateryStub import EateryStub -from api.datatype.Event import Event -from api.dfg.preparation.datatype.OverrideEatery import OverrideEatery -from api.dfg.preparation.datatype.CornellDiningEatery import CornellDiningEatery -from api.dfg.preparation.datatype.OverrideEvent import OverrideEvent - -T = TypeVar('T') - -class AssembleEateries(DfgNode): - - def __init__(self, stubs: DfgNode, cornell_dining: DfgNode, override: DfgNode): - self.stubs = stubs - self.cornell_dining = cornell_dining - self.override = override - - def __call__(self, *args, **kwargs): - assembled_eateries = [] - eatery_stubs = self.stubs(*args, **kwargs) - cornell_dining_eateries = self.cornell_dining(*args, **kwargs) - override_eateries = self.override(*args, **kwargs) - - for stub in eatery_stubs: - cornell_dining_eatery = next((eatery for eatery in cornell_dining_eateries if eatery.name == stub.name), None) - override_eatery = next((eatery for eatery in override_eateries if eatery.name == stub.name), None) - assembled_eateries.append(AssembleEateries.preparation_to_eatery( - stub=stub, - cornell_dining_eatery=cornell_dining_eatery, - override_eatery=override_eatery - )) - return assembled_eateries - - @staticmethod - def preparation_to_eatery( - stub: EateryStub, - cornell_dining_eatery: Optional[CornellDiningEatery], - override_eatery: Optional[OverrideEatery] - ) -> Eatery: - return Eatery( - id=stub.id, - name=stub.name, - campus_area=AssembleEateries.fetch_field_precedence_first( - None if override_eatery is None else override_eatery.campus_area, - None if cornell_dining_eatery is None else cornell_dining_eatery.campus_area), - events=AssembleEateries.events_with_precedence( - None if override_eatery is None else override_eatery.known_events, - None if cornell_dining_eatery is None else cornell_dining_eatery.known_events), - latitude=AssembleEateries.fetch_field_precedence_first( - None if override_eatery is None else override_eatery.latitude, - None if cornell_dining_eatery is None else cornell_dining_eatery.latitude), - longitude=AssembleEateries.fetch_field_precedence_first( - None if override_eatery is None else override_eatery.longitude, - None if cornell_dining_eatery is None else cornell_dining_eatery.longitude), - payment_methods=AssembleEateries.fetch_field_precedence_first( - None if override_eatery is None else override_eatery.payment_methods, - None if cornell_dining_eatery is None else cornell_dining_eatery.payment_methods), - location=AssembleEateries.fetch_field_precedence_first( - None if override_eatery is None else override_eatery.location, - None if cornell_dining_eatery is None else cornell_dining_eatery.location), - online_order=AssembleEateries.fetch_field_precedence_first( - None if override_eatery is None else override_eatery.online_order, - None if cornell_dining_eatery is None else cornell_dining_eatery.online_order), - online_order_url=AssembleEateries.fetch_field_precedence_first( - None if override_eatery is None else override_eatery.online_order_url, - None if cornell_dining_eatery is None else cornell_dining_eatery.online_order_url) - ) - - @staticmethod - def events_with_precedence( - optional_override_events: Optional[list[OverrideEvent]], - optional_cornell_dining_events: Optional[list[Event]] - ): - override_events = [] if optional_override_events is None else optional_override_events - cornell_dining_events = [] if optional_cornell_dining_events is None else optional_cornell_dining_events - events = [] - - for event in cornell_dining_events: - potential_override_event = next((override_event for override_event in override_events if - override_event.canonical_date == event.canonical_date and - override_event.description == event.description - ), None) - if potential_override_event is not None: - events.append(event) - for event in override_events: - if event.exists: - events.append(Event( - description=event.description, - canonical_date=event.canonical_date, - start_timestamp=event.start_timestamp, - end_timestamp=event.end_timestamp, - menu=event.menu - )) - return events - - @staticmethod - def fetch_field_precedence_first( - primary: Optional[T], - secondary: Optional[T], - ) -> Optional[T]: - return secondary if primary is None else primary - - def description(self): - return "AssembleEateries" diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index eec8571..0b58bcb 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -10,7 +10,6 @@ from datetime import date - class CornellDiningNow(DfgNode): CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" @@ -27,7 +26,6 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: eateries = [] for json_eatery in json_eateries: eateries.append(CornellDiningNow.parse_eatery(json_eatery)) - return eateries else: @@ -41,8 +39,7 @@ def parse_eatery(json_eatery: dict) -> Eatery: } return Eatery( id=CornellDiningNow.dining_id_to_internal_id(json_eatery["id"]), - about=json_eatery["about"], - about_short=json_eatery["aboutshort"], + name=json_eatery["name"], campus_area=json_eatery["campusArea"]["descrshort"], latitude=json_eatery["latitude"], longitude=json_eatery["longitude"], @@ -88,7 +85,8 @@ def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, description=json_event["descr"], start_timestamp=json_event["startTimestamp"], end_timestamp=json_event["endTimestamp"], - menu=CornellDiningNow.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe) + menu=CornellDiningNow.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe), + exists=True )) return events diff --git a/api/dfg/EateryStubs.py b/api/dfg/EateryStubs.py index 9a02705..7108bae 100644 --- a/api/dfg/EateryStubs.py +++ b/api/dfg/EateryStubs.py @@ -5,4 +5,4 @@ class EateryStubs(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: - return [Eatery(id = id) for id in EateryID] + return [Eatery(id = id) for id in EateryID if id != 0] diff --git a/api/dfg/EateryToJson.py b/api/dfg/EateryToJson.py index 6842c84..07d54b8 100644 --- a/api/dfg/EateryToJson.py +++ b/api/dfg/EateryToJson.py @@ -1,7 +1,7 @@ from typing import Union -from api.dfg.waittimes.datatype.EateryWithWaitTimes import EateryWithWaitTimes from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery class EateryToJson(DfgNode): def __init__(self, child: DfgNode): @@ -11,8 +11,11 @@ def __call__(self, *args, **kwargs): result = self.child(*args, **kwargs) return EateryToJson.to_json(result, *args, **kwargs) + def children(self): + return [self.child] + @staticmethod - def to_json(obj: Union[list, dict, EateryWithWaitTimes], *args, **kwargs): + def to_json(obj: Union[list, dict, Eatery], *args, **kwargs): if isinstance(obj, list): return [ EateryToJson.to_json(elem, *args, **kwargs) diff --git a/api/dfg/LeftMerge.py b/api/dfg/LeftMerge.py index 2bd2425..23079f9 100644 --- a/api/dfg/LeftMerge.py +++ b/api/dfg/LeftMerge.py @@ -2,7 +2,7 @@ from api.datatype.Eatery import Eatery from api.datatype.Event import Event -from api.datatype.WaitTimesByDay import WaitTimesByDay +from api.datatype.WaitTimesDay import WaitTimesDay from api.dfg.DfgNode import DfgNode from api.dfg.EateryToJson import EateryToJson @@ -14,6 +14,9 @@ def __init__(self, left: DfgNode, right: DfgNode): self.left = left self.right = right + def children(self): + return [self.left, self.right] + def __call__(self, *args, **kwargs): merged_eateries = [] right_eateries = self.right(*args, **kwargs) @@ -23,13 +26,13 @@ def __call__(self, *args, **kwargs): merged_eateries.append(left_eatery) else: right_eateries = [right_eatery for right_eatery in right_eateries if right_eatery.id != left_eatery.id] + merged_eateries.append(LeftMerge.merge_eateries(left_eatery, updated_eatery)) - merged_eateries.add(right_eateries) return merged_eateries @staticmethod def merge_eateries(left: Eatery, right: Eatery): - merged_events = LeftMerge.merge_events(left.events, right.events) + merged_events = LeftMerge.merge_events(left.events(), right.events()) return Eatery( id=left.id, name=LeftMerge.merge_fields(left.name, right.name), @@ -56,23 +59,23 @@ def merge_events(left: list[Event], right: list[Event]): right_event.canonical_date != left_event.canonical_date or right_event.description != left_event.description ] - merged_events.add(left_event) - merged_events.add(right) + merged_events.append(left_event) + merged_events.extend(right) return merged_events @staticmethod - def merge_and_filter_waittimes(left: list[WaitTimesByDay], right: list[WaitTimesByDay], events: list[Event]) -> list[WaitTimesByDay]: + def merge_and_filter_waittimes(left: list[WaitTimesDay], right: list[WaitTimesDay], events: list[Event]) -> list[WaitTimesDay]: wait_times_filtered = [] wait_times = LeftMerge.merge_fields(left, right) if wait_times is None: return None for wait_times_by_day in wait_times: - wait_times_by_day_filtered = [] - for wait_time in wait_times_by_day.daily_wait_times: - if any([wait_time.timestamp in event for event in events]): - wait_times_by_day_filtered.append(wait_time) + filtered_data = [] + for wait_time_data in wait_times_by_day.data: + if any([wait_time_data.timestamp in event for event in events]): + filtered_data.append(wait_time_data) wait_times_filtered.append( - WaitTimesByDay(canonical_date=wait_times_by_day.canonical_date, wait_times=wait_times_by_day_filtered) + WaitTimesDay(canonical_date=wait_times_by_day.canonical_date, data=filtered_data) ) return wait_times_filtered @@ -81,4 +84,4 @@ def children(self): return self.children def description(self): - return "Merge" \ No newline at end of file + return "LeftMerge" \ No newline at end of file diff --git a/api/dfg/WaitTimes.py b/api/dfg/WaitTimes.py index 441b839..163ca38 100644 --- a/api/dfg/WaitTimes.py +++ b/api/dfg/WaitTimes.py @@ -4,7 +4,7 @@ import pytz from api.datatype.Eatery import Eatery, EateryID -from api.datatype.WaitTimesByDay import WaitTimesByDay +from api.datatype.WaitTimesDay import WaitTimesDay from api.datatype.WaitTime import WaitTime from api.dfg.DfgNode import DfgNode @@ -26,11 +26,11 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: past_day = date - timedelta(days = 7 * i) past_days.append(past_day) transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ - .values("id", "block_end_time") \ + .values("eatery_id", "block_end_time") \ .annotate(transaction_avg=Avg("transaction_count")) for unit in transaction_avg_counts: transaction_history = TransactionHistorySerializer(unit) - eatery_id = EateryID(transaction_history.data['id']) + eatery_id = EateryID(transaction_history.data['eatery_id']) eatery_ids.add(eatery_id) if eatery_id not in transactions_on_date: transactions_on_date[eatery_id] = [] @@ -43,10 +43,10 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: eatery_wait_times_by_day = [] for date in transactions_by_date: if eatery_id in transactions_by_date[date]: - eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day(eatery_id)) + eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day(eatery_id, date, transactions_by_date[date][eatery_id])) eateries.append( Eatery( - eatery_id=eatery_id, + id=eatery_id, wait_times = eatery_wait_times_by_day ) ) @@ -87,8 +87,8 @@ def generate_eatery_wait_times_by_day( eatery_id: int, date: date, transactions: list[TransactionHistorySerializer] - ) -> WaitTimesByDay: - wait_times = [] + ) -> WaitTimesDay: + wait_times_data = [] customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] for index in reversed(range(0, len(transactions))): base_times = WaitTimes.base_time_to_get_food(eatery_id) @@ -112,13 +112,13 @@ def generate_eatery_wait_times_by_day( customers_waiting_in_line.append(0.0) block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() timestamp = int(WaitTimes.timestamp_combined(date, block_end_time) - 5 * 60 / 2) - wait_times.insert(0, WaitTime( + wait_times_data.insert(0, WaitTime( timestamp=timestamp, wait_time_low = wait_time_low, wait_time_expected= wait_time_expected, wait_time_high = wait_time_high)) - return WaitTimesByDay(date, wait_times) + return WaitTimesDay(canonical_date = date, data = wait_times_data) @staticmethod def timestamp_combined(date: datetime.date, time: datetime.time): @@ -130,7 +130,5 @@ def timestamp_combined(date: datetime.date, time: datetime.time): tz = pytz.timezone('America/New_York') return int(tz.localize(datetime.combine(date, time)).timestamp()) - - def description(self): - return "FetchTransactionCounts" + return "WaitTimes" diff --git a/api/utils.py b/api/utils.py deleted file mode 100644 index 4b525b1..0000000 --- a/api/utils.py +++ /dev/null @@ -1,4 +0,0 @@ - - - -def EATERY_ID_TO_NAME(): diff --git a/api/views.py b/api/views.py index e87ec3f..3033be7 100644 --- a/api/views.py +++ b/api/views.py @@ -2,42 +2,41 @@ import pytz from django.http import JsonResponse -from api.dfg.WaitTimes import FetchTransactionCounts from api.dfg.CornellDiningNow import CornellDiningNow from api.dfg.EateryStubs import EateryStubs from api.dfg.ExternalEateries import ExternalEateries -from api.dfg.AssembleEateries import AssembleEateries -from api.dfg.AddWaitTimesToEateries import AddWaitTimesToEateries - from api.dfg.DictResponseWrapper import DictResponseWrapper from api.dfg.EateryToJson import EateryToJson from api.dfg.InMemoryCache import InMemoryCache +from api.dfg.LeftMerge import LeftMerge +from api.dfg.WaitTimes import WaitTimes dataflow_graph = DictResponseWrapper( EateryToJson( InMemoryCache( - AddWaitTimesToEateries( - eateries=AssembleEateries( - stubs=EateryStubs(), - cornell_dining=CornellDiningNow(), - override=ExternalEateries() - ), - transaction_counts = FetchTransactionCounts() + LeftMerge( + WaitTimes(), + LeftMerge( + ExternalEateries(), + LeftMerge( + CornellDiningNow(), + EateryStubs() + ) + ) ) ) ), re_raise_exceptions=True ) - def index(request): tzinfo = pytz.timezone("US/Eastern") reload = request.GET.get('reload') result = dataflow_graph( tzinfo=tzinfo, - reload=reload is not None and reload is not "false", + reload=reload is not None and reload != "false", start=date.today(), end=date.today() + timedelta(days=7) ) diff --git a/eateries/models.py b/eateries/models.py index af5f150..49be2af 100644 --- a/eateries/models.py +++ b/eateries/models.py @@ -1,11 +1,13 @@ from django.db import models # Create your models here. -class Eatery(models.Model): - class Meta: - unique_together = ('name', 'block_end_time', 'canonical_date') - indexes = [models.Index(fields = ['canonical_date'])] - ud - canonical_date = models.DateField() - block_end_time = models.TimeField() - transaction_count = models.IntegerField() \ No newline at end of file +# class Eatery(models.Model): +# class Meta: +# unique_together = ('name', 'block_end_time', 'canonical_date') +# indexes = [models.Index(fields = ['canonical_date'])] +# canonical_date = models.DateField() +# block_end_time = models.TimeField() +# transaction_count = models.IntegerField() + + +# TODO: Models for hard-coded eateries \ No newline at end of file diff --git a/eatery_blue_backend/settings.py b/eatery_blue_backend/settings.py index 113c456..1e806a6 100644 --- a/eatery_blue_backend/settings.py +++ b/eatery_blue_backend/settings.py @@ -25,7 +25,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ["d706-2601-187-8400-2076-1158-3d93-7b45-1456.ngrok.io", "127.0.0.1"] +ALLOWED_HOSTS = ["d706-2601-187-8400-2076-1158-3d93-7b45-1456.ngrok.io", "127.0.0.1", "localhost"] # Application definition diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index 9e165bd..8838497 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -5,6 +5,8 @@ from random import randrange import pytz +from api.datatype.Eatery import EateryID + from transactions.models import TransactionHistory class UpdateTransactionsController: @@ -13,72 +15,72 @@ class UpdateTransactionsController: def vendor_name_to_internal_id(vendor_eatery_name): vendor_eatery_name = ''.join(c.lower() for c in vendor_eatery_name if c.isalpha()) if vendor_eatery_name == "bearnecessities": - return 4 + return EateryID.BEAR_NECESSITIES elif vendor_eatery_name == "northstarmarketplace": - return 25 + return EateryID.NORTH_STAR_DINING elif vendor_eatery_name == "jansensmarket": - return 19 + return EateryID.JANSENS_MARKET elif vendor_eatery_name == "stockinghallcafe" or vendor_eatery_name == "stockinghall": - return 11 + return EateryID.DAIRY_BAR elif vendor_eatery_name == "marthas": - return 22 + return EateryID.MARTHAS_CAFE elif vendor_eatery_name == "cafejennie": - return 8 + return EateryID.CAFE_JENNIE elif vendor_eatery_name == "goldiescafe": - return 14 + return EateryID.GOLDIES_CAFE elif vendor_eatery_name == "alicecookhouse": - return 10 + return EateryID.COOK_HOUSE elif vendor_eatery_name == "carlbeckerhouse": - return 5 + return EateryID.BECKER_HOUSE elif vendor_eatery_name == "duffield": - return 23 + return EateryID.MATTINS_CAFE elif vendor_eatery_name == "greendragon": - return 15 + return EateryID.GREEN_DRAGON elif vendor_eatery_name == "trillium": - return 32 + return EateryID.TRILLIUM elif vendor_eatery_name == "olinlibecafe": - return 2 + return EateryID.LIBE_CAFE elif vendor_eatery_name == "carolscafe": - return 9 + return EateryID.CAROLS_CAFE elif vendor_eatery_name == "statlerterrace": - return 33 + return EateryID.TERRACE elif vendor_eatery_name == "busstopbagels": - return 7 + return EateryID.BUS_STOP_BAGELS elif vendor_eatery_name == "kosher": - return 1 + return EateryID.ONE_ZERO_FOUR_WEST elif vendor_eatery_name == "jansensatbethehouse": - return 18 + return EateryID.BETHE_HOUSE elif vendor_eatery_name == "keetonhouse": - return 20 + return EateryID.KEETON_HOUSE elif vendor_eatery_name == "rpme": - return 28 + return EateryID.RPCC elif vendor_eatery_name == "rosehouse": - return 29 + return EateryID.ROSE_HOUSE elif vendor_eatery_name == "risley": - return 27 + return EateryID.RISLEY elif vendor_eatery_name == "frannysft": - return 13 + return EateryID.FRANNYS elif vendor_eatery_name == "mccormicks": - return 24 + return EateryID.MCCORMICKS elif vendor_eatery_name == "sage": - return 3 + return EateryID.ATRIUM_CAFE elif vendor_eatery_name == "straightmarket": - return 31 + return EateryID.STRAIGHT_FROM_THE_MARKET elif vendor_eatery_name == "crossingscafe": - return 12 + return EateryID.CROSSINGS_CAFE elif vendor_eatery_name == "okenshields": - return 26 + return EateryID.OKENSHIELDS elif vendor_eatery_name == "bigredbarn": - return 6 + return EateryID.BIG_RED_BARN elif vendor_eatery_name == "rustys": - return 30 + return EateryID.RUSTYS elif vendor_eatery_name == "manncafe": - return 21 + return EateryID.MANN_CAFE elif vendor_eatery_name == "statlermacs": - return 34 + return EateryID.MACS_CAFE else: # TODO: Add a slack notif / flag that a wait time location was not recognized - return -1 + return EateryID.NULL def __init__(self, data): self._data = data @@ -106,13 +108,13 @@ def process(self): num_inserted = 0 ignored_names = set() for place in self._data["UNITS"]: - internal_id = UpdateTransactionsController.vendor_name_to_internal_id(place["UNIT_NAME"]) - if id < 0: + internal_id = UpdateTransactionsController.vendor_name_to_internal_id(place["UNIT_NAME"]).value + if internal_id < 0: ignored_names.add(place["UNIT_NAME"]) else: num_inserted += 1 try: - TransactionHistory.objects.create(id = internal_id, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) + TransactionHistory.objects.create(eatery_id = internal_id, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) except: num_inserted -= 1 return { diff --git a/transactions/migrations/0009_alter_transactionhistory_unique_together_and_more.py b/transactions/migrations/0009_alter_transactionhistory_unique_together_and_more.py new file mode 100644 index 0000000..98d905a --- /dev/null +++ b/transactions/migrations/0009_alter_transactionhistory_unique_together_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.0 on 2022-01-03 22:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='transactionhistory', + unique_together=set(), + ), + migrations.AddField( + model_name='transactionhistory', + name='eatery_id', + field=models.IntegerField(default=3), + preserve_default=False, + ), + migrations.AlterUniqueTogether( + name='transactionhistory', + unique_together={('eatery_id', 'block_end_time', 'canonical_date')}, + ), + migrations.RemoveField( + model_name='transactionhistory', + name='name', + ), + ] diff --git a/transactions/models.py b/transactions/models.py index 78bdfe4..2b994ab 100644 --- a/transactions/models.py +++ b/transactions/models.py @@ -5,9 +5,9 @@ # [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] class TransactionHistory(models.Model): class Meta: - unique_together = ('id', 'block_end_time', 'canonical_date') + unique_together = ('eatery_id', 'block_end_time', 'canonical_date') indexes = [models.Index(fields = ['canonical_date'])] - id = models.IntegerField() + eatery_id = models.IntegerField() canonical_date = models.DateField() block_end_time = models.TimeField() transaction_count = models.IntegerField() \ No newline at end of file diff --git a/transactions/serializers.py b/transactions/serializers.py index 2ebaa10..0356459 100644 --- a/transactions/serializers.py +++ b/transactions/serializers.py @@ -17,7 +17,7 @@ def get_transaction_avg(self, obj): class Meta: model = TransactionHistory fields = ( - "name", + "eatery_id", "canonical_date", "block_end_time", "transaction_avg" From fe91f3becfdf78ec5c00345e585f92e188ad8e65 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Wed, 5 Jan 2022 15:59:38 -0500 Subject: [PATCH 024/305] Update LeftMerge to use json. Add FilterWaitTime dfg node --- api/datatype/Eatery.py | 24 ++- api/datatype/Event.py | 16 +- api/datatype/Menu.py | 6 + api/datatype/MenuCategory.py | 7 + api/datatype/MenuItem.py | 7 + api/datatype/WaitTime.py | 9 + api/datatype/WaitTimesDay.py | 12 +- api/dfg/CornellDiningNow.py | 5 +- api/dfg/EateryFromJson.py | 26 +++ api/dfg/EateryStubs.py | 2 +- api/dfg/EateryToJson.py | 9 +- api/dfg/ExternalEateries.py | 3 +- api/dfg/GoogleSheetsEateries.py | 178 ------------------ api/dfg/LeftMerge.py | 87 --------- api/dfg/LeftMergeById.py | 49 +++++ api/dfg/WaitTimeFilter.py | 34 ++++ api/dfg/WaitTimes.py | 14 +- api/dfg/macros/LeftMergeEateries.py | 19 ++ api/views.py | 19 +- .../update_transactions_controller.py | 4 +- 20 files changed, 224 insertions(+), 306 deletions(-) create mode 100644 api/dfg/EateryFromJson.py delete mode 100644 api/dfg/GoogleSheetsEateries.py delete mode 100644 api/dfg/LeftMerge.py create mode 100644 api/dfg/LeftMergeById.py create mode 100644 api/dfg/WaitTimeFilter.py create mode 100644 api/dfg/macros/LeftMergeEateries.py diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index fcc0f9c..15dbc61 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -8,7 +8,6 @@ from api.datatype.WaitTimesDay import WaitTimesDay class EateryID(Enum): - NULL = 0 ONE_ZERO_FOUR_WEST = 1 LIBE_CAFE = 2 ATRIUM_CAFE = 3 @@ -94,13 +93,32 @@ def to_json( "id": self.id.value, "name": self.name, "campus_area": self.campus_area, - "events": [event.to_json() for event in self.events(tzinfo, start, end)], + "events": None if self.known_events is None else [event.to_json() for event in self.events(tzinfo, start, end)], "latitude": self.latitude, "longitude": self.longitude, "payment_methods": None if self.payment_methods is None else [payment_method for payment_method in self.payment_methods], "location": self.location, "online_order": self.online_order, "online_order_url": self.online_order_url, - "wait_times": [] if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times] + "wait_times": None if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times] } return eatery + + @staticmethod + def from_json(eatery_json): + return Eatery( + id = None if "id" not in eatery_json else EateryID(eatery_json["id"]), + name = None if "name" not in eatery_json else eatery_json["name"], + campus_area=None if "campus_area" not in eatery_json else eatery_json["campus_area"], + events = None if "events" not in eatery_json or eatery_json["events"] is None else [Event.from_json(event) for event in eatery_json["events"]], + latitude = None if "latitude" not in eatery_json else eatery_json["latitude"], + longitude = None if "longitude" not in eatery_json else eatery_json["longitude"], + payment_methods = None if "payment_methods" not in eatery_json else eatery_json["payment_methods"], + location = None if "location" not in eatery_json else eatery_json["location"], + online_order = None if "online_order" not in eatery_json else eatery_json["online_order"], + online_order_url = None if "online_order_url" not in eatery_json else eatery_json["online_order_url"], + wait_times = None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]] + ) + + def clone(self): + return Eatery.from_json(self.to_json()) \ No newline at end of file diff --git a/api/datatype/Event.py b/api/datatype/Event.py index a0ea762..f3a538f 100644 --- a/api/datatype/Event.py +++ b/api/datatype/Event.py @@ -14,15 +14,13 @@ def __init__( canonical_date: date, start_timestamp: int, end_timestamp: int, - menu: Menu, - exists: bool + menu: Menu ): self.description = description self.canonical_date = canonical_date self.start_timestamp = start_timestamp self.end_timestamp = end_timestamp self.menu = menu - self.exists = exists def to_json(self): return { @@ -33,6 +31,16 @@ def to_json(self): "menu": self.menu.to_json() } + @staticmethod + def from_json(event_json): + return Event( + description=event_json["description"], + canonical_date=date.fromisoformat(event_json["canonical_date"]), + start_timestamp=event_json["start_timestamp"], + end_timestamp=event_json["end_timestamp"], + menu=Menu.from_json(event_json["menu"]) + ) + def __contains__(self, item: int): return self.start_timestamp <= item <= self.end_timestamp @@ -44,7 +52,7 @@ def _combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): if events is None: return [] - + if start is None and end is None: return events diff --git a/api/datatype/Menu.py b/api/datatype/Menu.py index dd0f742..c48e100 100644 --- a/api/datatype/Menu.py +++ b/api/datatype/Menu.py @@ -7,3 +7,9 @@ def __init__(self, categories: list[MenuCategory]): def to_json(self): return [category.to_json() for category in self.categories] + + @staticmethod + def from_json(menu_json): + return Menu( + categories=[MenuCategory.from_json(category) for category in menu_json] + ) diff --git a/api/datatype/MenuCategory.py b/api/datatype/MenuCategory.py index 3a2fee3..b522954 100644 --- a/api/datatype/MenuCategory.py +++ b/api/datatype/MenuCategory.py @@ -11,3 +11,10 @@ def to_json(self): "category": self.category, "items": [item.to_json() for item in self.items] } + + @staticmethod + def from_json(category_json): + return MenuCategory( + category=category_json["category"], + items=[MenuItem.from_json(item) for item in category_json["items"]] + ) diff --git a/api/datatype/MenuItem.py b/api/datatype/MenuItem.py index c171cf5..cfd7756 100644 --- a/api/datatype/MenuItem.py +++ b/api/datatype/MenuItem.py @@ -20,3 +20,10 @@ def to_json(self): "healthy": self.healthy, "name": self.name } + + @staticmethod + def from_json(item_json): + return MenuItem( + healthy=item_json["healthy"], + name=item_json["name"] + ) diff --git a/api/datatype/WaitTime.py b/api/datatype/WaitTime.py index 5bf4d5b..9825c3b 100644 --- a/api/datatype/WaitTime.py +++ b/api/datatype/WaitTime.py @@ -18,4 +18,13 @@ def to_json(self): "wait_time_expected": self.wait_time_expected, "wait_time_high": self.wait_time_high } + + @staticmethod + def from_json(wait_time_json): + return WaitTime( + timestamp = wait_time_json["timestamp"], + wait_time_low=wait_time_json["wait_time_low"], + wait_time_expected=wait_time_json["wait_time_expected"], + wait_time_high=wait_time_json["wait_time_high"] + ) diff --git a/api/datatype/WaitTimesDay.py b/api/datatype/WaitTimesDay.py index f4d1d32..2326b85 100644 --- a/api/datatype/WaitTimesDay.py +++ b/api/datatype/WaitTimesDay.py @@ -1,11 +1,12 @@ +from datetime import date from .WaitTime import WaitTime class WaitTimesDay: def __init__( self, - canonical_date: str, + canonical_date: date, data: list[WaitTime] ): self.canonical_date = canonical_date @@ -13,7 +14,14 @@ def __init__( def to_json(self): return { - "canonical_date": self.canonical_date, + "canonical_date": str(self.canonical_date), "data": [wait_time.to_json() for wait_time in self.data] } + @staticmethod + def from_json(wait_times_day_json): + return WaitTimesDay( + canonical_date=date.fromisoformat(wait_times_day_json["canonical_date"]), + data=[WaitTime.from_json(wait_time) for wait_time in wait_times_day_json["data"]] + ) + diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index 0b58bcb..dd07ac4 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -85,8 +85,7 @@ def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, description=json_event["descr"], start_timestamp=json_event["startTimestamp"], end_timestamp=json_event["endTimestamp"], - menu=CornellDiningNow.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe), - exists=True + menu=CornellDiningNow.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe) )) return events @@ -198,7 +197,7 @@ def dining_id_to_internal_id(id: int): elif id == 23: return EateryID.TERRACE else: - return EateryID.NULL + return None def description(self): return "CornellDiningNow" diff --git a/api/dfg/EateryFromJson.py b/api/dfg/EateryFromJson.py new file mode 100644 index 0000000..5b5feb6 --- /dev/null +++ b/api/dfg/EateryFromJson.py @@ -0,0 +1,26 @@ +from typing import Union + +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery + +class EateryFromJson(DfgNode): + + def __init__(self, child: DfgNode): + self.child = child + + def __call__(self, *args, **kwargs): + result = self.child(*args, **kwargs) + return EateryFromJson.from_json(result, *args, **kwargs) + + def children(self): + return [self.child] + + @staticmethod + def from_json(obj: Union[list, dict], *args, **kwargs): + if isinstance(obj, list): + return [ + EateryFromJson.from_json(elem, *args, **kwargs) + for elem in obj + ] + else: + return Eatery.from_json(obj) \ No newline at end of file diff --git a/api/dfg/EateryStubs.py b/api/dfg/EateryStubs.py index 7108bae..9a02705 100644 --- a/api/dfg/EateryStubs.py +++ b/api/dfg/EateryStubs.py @@ -5,4 +5,4 @@ class EateryStubs(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: - return [Eatery(id = id) for id in EateryID if id != 0] + return [Eatery(id = id) for id in EateryID] diff --git a/api/dfg/EateryToJson.py b/api/dfg/EateryToJson.py index 07d54b8..c9bc12b 100644 --- a/api/dfg/EateryToJson.py +++ b/api/dfg/EateryToJson.py @@ -15,19 +15,12 @@ def children(self): return [self.child] @staticmethod - def to_json(obj: Union[list, dict, Eatery], *args, **kwargs): + def to_json(obj: Union[list, Eatery], *args, **kwargs): if isinstance(obj, list): return [ EateryToJson.to_json(elem, *args, **kwargs) for elem in obj ] - - elif isinstance(obj, dict): - return { - key: EateryToJson.to_json(value, *args, **kwargs) - for key, value in obj.items() - } - else: return obj.to_json( tzinfo=kwargs.get("tzinfo"), diff --git a/api/dfg/ExternalEateries.py b/api/dfg/ExternalEateries.py index 2d04750..fe6172c 100644 --- a/api/dfg/ExternalEateries.py +++ b/api/dfg/ExternalEateries.py @@ -132,8 +132,7 @@ def eatery_events_from_json( end_timestamp=ExternalEateries.timestamp_combined( current, ExternalEateries.time_since_midnight(event_template["end"]) - ), - exists=True + ) ) resolved_events.append(event) diff --git a/api/dfg/GoogleSheetsEateries.py b/api/dfg/GoogleSheetsEateries.py deleted file mode 100644 index 5267c6f..0000000 --- a/api/dfg/GoogleSheetsEateries.py +++ /dev/null @@ -1,178 +0,0 @@ -import datetime -import json -import os -import re -from typing import Optional - -import pytz - -from api.datatype.Eatery import Eatery -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem -from api.dfg.DfgNode import DfgNode - -import os.path - -from google.auth.transport.requests import Request -from google.oauth2.credentials import Credentials -from google_auth_oauthlib.flow import InstalledAppFlow -from googleapiclient.discovery import build -from googleapiclient.errors import HttpError - - -class GoogleSheetsEateries(DfgNode): - - def __init__( - self, - spreadsheet_id: str, - external_eateries_path: str = 'static_sources/external_eateries.json', - credentials_path: str = 'credentials.json', - cached_token_path: str = 'token.json' - ): - self.spreadsheet_id = spreadsheet_id - self.external_eateries_path = external_eateries_path - self.credentials_path = credentials_path - self.token_cache_path = cached_token_path - - def __call__(self, *args, **kwargs): - eateries = [] - - # with open(self.external_eateries_path) as f: - # json_eateries = json.load(f)["eateries"] - - # for json_eatery in json_eateries: - # eateries.append(self.eatery_from_json(json_eatery)) - - return eateries - - def eatery_from_json(self, json_eatery: dict) -> Eatery: - return Eatery( - name=json_eatery["name"], - campus_area=json_eatery["campusArea"]["descrshort"], - events=self.events_from_google_sheets( - table_name=json_eatery["name"], - dining_items=json_eatery["dining_items"] - ), - latitude=json_eatery["coordinates"]["latitude"], - longitude=json_eatery["coordinates"]["longitude"], - ) - - def events_from_google_sheets( - self, - table_name: str, - dining_items: list - ) -> list[Event]: - scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"] - creds = None - if os.path.exists(self.token_cache_path): - creds = Credentials.from_authorized_user_file(self.token_cache_path, scopes) - - if not creds or not creds.valid: - if creds and creds.expired and creds.refresh_token: - creds.refresh(Request()) - else: - flow = InstalledAppFlow.from_client_secrets_file("credentials.json", scopes) - creds = flow.run_local_server(port=0) - - with open(self.token_cache_path, "w") as token: - token.write(creds.to_json()) - - try: - service = build("sheets", "v4", credentials=creds) - sheet = service.spreadsheets() - result = sheet.values().get( - spreadsheetId=self.spreadsheet_id, - range=f"{table_name}!A:C" - ).execute() - values = result.get("values", []) - - except HttpError as err: - print(f"{self}: {err}") - return [] - - events = [] - for row in values: - event = self.parse_row(row, dining_items) - if event is not None: - events.append(event) - - return events - - def parse_row(self, row: list[str], dining_items: list) -> Optional[Event]: - if len(row) != 3: - return None - - try: - date = datetime.date.fromisoformat(row[0]) - - except ValueError: - return None - - start_time = self.parse_time(row[1]) - end_time = self.parse_time(row[2]) - - if start_time is None or end_time is None: - return None - - return Event( - canonical_date=date, - start_timestamp=GoogleSheetsEateries.timestamp_combined(date, start_time), - end_timestamp=GoogleSheetsEateries.timestamp_combined(date, end_time), - menu=GoogleSheetsEateries.eatery_menu_from_json(dining_items) - ) - - def parse_time(self, time_str: str) -> Optional[datetime.time]: - # time_str is like 10:00 AM or 3:00 PM - match = re.fullmatch(r'([0-9]?[0-9]):([0-9][0-9]) ([AP]M)', time_str) - if not match: - return None - - hours = int(match.group(1)) - minutes = int(match.group(2)) - is_pm = match.group(3) == "pm" - try: - return datetime.time( - hour=hours + (12 if is_pm else 0), - minute=minutes - ) - - except Exception as e: - print(f"{self}: e") - return None - - @staticmethod - def eatery_menu_from_json(json_dining_items: list) -> Menu: - category_map = {} - for item in json_dining_items: - if item['category'] not in category_map: - category_map[item['category']] = [] - category_map[item['category']].append(MenuItem(healthy=item['healthy'], name = item['item'])) - categories = [] - for category_name in category_map: - categories.append(MenuCategory(category_name, category_map[category_name])) - return Menu(categories=categories) - - @staticmethod - def timestamp_combined(date: datetime.date, time: datetime.time): - """ - Returns the Unix (UTC) timestamp of the combined (date, time) in the - New York timezone. - """ - - tz = pytz.timezone('America/New_York') - return int(tz.localize(datetime.datetime.combine(date, time)).timestamp()) - - def description(self): - return "ExternalEateries" - - -if __name__ == "__main__": - from api.dfg.EateryToJson import EateryToJson - - dfg = EateryToJson(GoogleSheetsEateries( - spreadsheet_id="1ImfeTUA6I1Ub-aavgIW53Pf7EVB694f1294NPSCRd5c", - )) - - print(json.dumps(dfg(), indent=2)) diff --git a/api/dfg/LeftMerge.py b/api/dfg/LeftMerge.py deleted file mode 100644 index 23079f9..0000000 --- a/api/dfg/LeftMerge.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import TypeVar, Optional - -from api.datatype.Eatery import Eatery -from api.datatype.Event import Event -from api.datatype.WaitTimesDay import WaitTimesDay -from api.dfg.DfgNode import DfgNode -from api.dfg.EateryToJson import EateryToJson - -T = TypeVar('T') - -class LeftMerge(DfgNode): - - def __init__(self, left: DfgNode, right: DfgNode): - self.left = left - self.right = right - - def children(self): - return [self.left, self.right] - - def __call__(self, *args, **kwargs): - merged_eateries = [] - right_eateries = self.right(*args, **kwargs) - for left_eatery in self.left(*args, **kwargs): - updated_eatery = next((right_eatery for right_eatery in right_eateries if right_eatery.id == left_eatery.id), None) - if updated_eatery == None: - merged_eateries.append(left_eatery) - else: - right_eateries = [right_eatery for right_eatery in right_eateries if right_eatery.id != left_eatery.id] - merged_eateries.append(LeftMerge.merge_eateries(left_eatery, updated_eatery)) - - return merged_eateries - - @staticmethod - def merge_eateries(left: Eatery, right: Eatery): - merged_events = LeftMerge.merge_events(left.events(), right.events()) - return Eatery( - id=left.id, - name=LeftMerge.merge_fields(left.name, right.name), - campus_area=LeftMerge.merge_fields(left.campus_area, right.campus_area), - events=merged_events, - latitude=LeftMerge.merge_fields(left.latitude, right.latitude), - longitude=LeftMerge.merge_fields(left.longitude, right.longitude), - location=LeftMerge.merge_fields(left.location, right.location), - online_order=LeftMerge.merge_fields(left.online_order, right.online_order), - online_order_url=LeftMerge.merge_fields(left.online_order_url, right.online_order_url), - wait_times=LeftMerge.merge_and_filter_waittimes(left.wait_times, right.wait_times, merged_events) - ) - - @staticmethod - def merge_fields(left: Optional[T], right: Optional[T]) -> Optional[T]: - return right if left is None else left - - @staticmethod - def merge_events(left: list[Event], right: list[Event]): - merged_events = [] - for left_event in left: - right = [ - right_event for right_event in right if - right_event.canonical_date != left_event.canonical_date or - right_event.description != left_event.description - ] - merged_events.append(left_event) - merged_events.extend(right) - return merged_events - - @staticmethod - def merge_and_filter_waittimes(left: list[WaitTimesDay], right: list[WaitTimesDay], events: list[Event]) -> list[WaitTimesDay]: - wait_times_filtered = [] - wait_times = LeftMerge.merge_fields(left, right) - if wait_times is None: - return None - for wait_times_by_day in wait_times: - filtered_data = [] - for wait_time_data in wait_times_by_day.data: - if any([wait_time_data.timestamp in event for event in events]): - filtered_data.append(wait_time_data) - wait_times_filtered.append( - WaitTimesDay(canonical_date=wait_times_by_day.canonical_date, data=filtered_data) - ) - return wait_times_filtered - - - def children(self): - return self.children - - def description(self): - return "LeftMerge" \ No newline at end of file diff --git a/api/dfg/LeftMergeById.py b/api/dfg/LeftMergeById.py new file mode 100644 index 0000000..96eb918 --- /dev/null +++ b/api/dfg/LeftMergeById.py @@ -0,0 +1,49 @@ +from api.dfg.DfgNode import DfgNode + +# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if conflict) +class LeftMergeById(DfgNode): + + def __init__(self, left: DfgNode, right: DfgNode): + self.left = left + self.right = right + + def children(self): + return [self.left, self.right] + + def __call__(self, *args, **kwargs): + left_lst = sorted(self.left(*args, **kwargs), key=lambda x: x["id"]) + right_lst = sorted(self.right(*args, **kwargs), key=lambda x: x["id"]) + left_json = _pop_first(left_lst) + right_json = _pop_first(right_lst) + merged_lst = [] + while left_json != None and right_json != None: + if left_json["id"] == right_json["id"]: + merged_json = {} + for key in right_json: + if right_json[key] != None: + merged_json[key] = right_json[key] + for key in left_json: + if left_json[key] != None: + merged_json[key] = left_json[key] + merged_lst.append(merged_json) + left_json = _pop_first(left_lst) + right_json = _pop_first(right_lst) + elif left_json["id"] < right_json["id"]: + merged_lst.append(left_json) + left_json = _pop_first(left_lst) + else: + merged_lst.append(right_json) + right_json = _pop_first(right_lst) + merged_lst.extend(left_lst) + merged_lst.extend(right_lst) + return merged_lst + + def description(self): + return "LeftMerge" + + +def _pop_first(lst: list): + try: + return lst.pop(0) + except IndexError: + return None \ No newline at end of file diff --git a/api/dfg/WaitTimeFilter.py b/api/dfg/WaitTimeFilter.py new file mode 100644 index 0000000..3e1b89b --- /dev/null +++ b/api/dfg/WaitTimeFilter.py @@ -0,0 +1,34 @@ +from api.datatype.WaitTimesDay import WaitTimesDay +from api.dfg.DfgNode import DfgNode + +# Removes all wait times that are not part of the eatery's events + +class WaitTimeFilter(DfgNode): + def __init__(self, child: DfgNode): + self.child = child + + def children(self): + return [self.child] + + def __call__(self, *args, **kwargs): + eateries = self.child(*args, **kwargs) + result = [] + for eatery in eateries: + if eatery.wait_times is None: + result.append(eatery.clone()) + else: + wait_times_filtered = [] + for day_wait_times in eatery.wait_times: + filtered_data = [] + for wait_time_data in day_wait_times.data: + eatery_events = eatery.events(tzinfo=kwargs.get("tzinfo"), start=kwargs.get("start"), end=kwargs.get("end")) + if any([wait_time_data.timestamp in event for event in eatery_events]): + filtered_data.append(wait_time_data) + wait_times_filtered.append(WaitTimesDay( + canonical_date=day_wait_times.canonical_date, + data=filtered_data + )) + eatery_clone = eatery.clone() + eatery_clone.wait_times = wait_times_filtered + result.append(eatery_clone) + return result \ No newline at end of file diff --git a/api/dfg/WaitTimes.py b/api/dfg/WaitTimes.py index 163ca38..e58540e 100644 --- a/api/dfg/WaitTimes.py +++ b/api/dfg/WaitTimes.py @@ -30,11 +30,12 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: .annotate(transaction_avg=Avg("transaction_count")) for unit in transaction_avg_counts: transaction_history = TransactionHistorySerializer(unit) - eatery_id = EateryID(transaction_history.data['eatery_id']) - eatery_ids.add(eatery_id) - if eatery_id not in transactions_on_date: - transactions_on_date[eatery_id] = [] - transactions_on_date[eatery_id].append(transaction_history) + if transaction_history.data['eatery_id'] != 0: + eatery_id = EateryID(transaction_history.data['eatery_id']) + eatery_ids.add(eatery_id) + if eatery_id not in transactions_on_date: + transactions_on_date[eatery_id] = [] + transactions_on_date[eatery_id].append(transaction_history) transactions_by_date[date] = transactions_on_date date += timedelta(days=1) @@ -98,9 +99,6 @@ def generate_eatery_wait_times_by_day( prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) if prev_bucket_guest_arrival > 9: # TODO: Send a slack error here instead - print(how_long_ago_guest_arrival) - print(date) - print(transactions[index].data) print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") else: customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index].data["transaction_avg"] diff --git a/api/dfg/macros/LeftMergeEateries.py b/api/dfg/macros/LeftMergeEateries.py new file mode 100644 index 0000000..cc77855 --- /dev/null +++ b/api/dfg/macros/LeftMergeEateries.py @@ -0,0 +1,19 @@ +from api.dfg.DfgNode import DfgNode +from api.dfg.EateryToJson import EateryToJson +from api.dfg.EateryFromJson import EateryFromJson +from api.dfg.LeftMergeById import LeftMergeById + +class LeftMergeEateries(DfgNode): + def __init__(self, left: DfgNode, right: DfgNode): + self.macro = EateryFromJson( + LeftMergeById( + EateryToJson(left), + EateryToJson(right) + ) + ) + + def children(self): + return self.macro.children() + + def __call__(self, *args, **kwargs): + return self.macro(*args, **kwargs) \ No newline at end of file diff --git a/api/views.py b/api/views.py index 3033be7..3a4f446 100644 --- a/api/views.py +++ b/api/views.py @@ -10,19 +10,22 @@ from api.dfg.DictResponseWrapper import DictResponseWrapper from api.dfg.EateryToJson import EateryToJson from api.dfg.InMemoryCache import InMemoryCache -from api.dfg.LeftMerge import LeftMerge +from api.dfg.macros.LeftMergeEateries import LeftMergeEateries from api.dfg.WaitTimes import WaitTimes +from api.dfg.WaitTimeFilter import WaitTimeFilter dataflow_graph = DictResponseWrapper( EateryToJson( InMemoryCache( - LeftMerge( - WaitTimes(), - LeftMerge( - ExternalEateries(), - LeftMerge( - CornellDiningNow(), - EateryStubs() + WaitTimeFilter( + LeftMergeEateries( + WaitTimes(), + LeftMergeEateries( + ExternalEateries(), + LeftMergeEateries( + CornellDiningNow(), + EateryStubs() + ) ) ) ) diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index 8838497..f5fcbd0 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -80,7 +80,7 @@ def vendor_name_to_internal_id(vendor_eatery_name): return EateryID.MACS_CAFE else: # TODO: Add a slack notif / flag that a wait time location was not recognized - return EateryID.NULL + return None def __init__(self, data): self._data = data @@ -109,7 +109,7 @@ def process(self): ignored_names = set() for place in self._data["UNITS"]: internal_id = UpdateTransactionsController.vendor_name_to_internal_id(place["UNIT_NAME"]).value - if internal_id < 0: + if internal_id == None: ignored_names.add(place["UNIT_NAME"]) else: num_inserted += 1 From 1cbdf1f006d4c505072ac2c9ec6af8385886d93c Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Wed, 5 Jan 2022 16:25:57 -0500 Subject: [PATCH 025/305] Description updates --- api/dfg/EateryFromJson.py | 5 ++++- api/dfg/EateryStubs.py | 3 +++ api/dfg/EateryToJson.py | 5 ++++- api/dfg/InMemoryCache.py | 4 ++++ api/dfg/LeftMergeById.py | 2 +- api/dfg/WaitTimeFilter.py | 5 ++++- api/dfg/macros/LeftMergeEateries.py | 5 ++++- 7 files changed, 24 insertions(+), 5 deletions(-) diff --git a/api/dfg/EateryFromJson.py b/api/dfg/EateryFromJson.py index 5b5feb6..53bbf4e 100644 --- a/api/dfg/EateryFromJson.py +++ b/api/dfg/EateryFromJson.py @@ -23,4 +23,7 @@ def from_json(obj: Union[list, dict], *args, **kwargs): for elem in obj ] else: - return Eatery.from_json(obj) \ No newline at end of file + return Eatery.from_json(obj) + + def description(self): + return "EateryFromJson" \ No newline at end of file diff --git a/api/dfg/EateryStubs.py b/api/dfg/EateryStubs.py index 9a02705..36e0e86 100644 --- a/api/dfg/EateryStubs.py +++ b/api/dfg/EateryStubs.py @@ -6,3 +6,6 @@ class EateryStubs(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: return [Eatery(id = id) for id in EateryID] + + def description(self): + return "EateryStubs" \ No newline at end of file diff --git a/api/dfg/EateryToJson.py b/api/dfg/EateryToJson.py index c9bc12b..574d1f7 100644 --- a/api/dfg/EateryToJson.py +++ b/api/dfg/EateryToJson.py @@ -26,4 +26,7 @@ def to_json(obj: Union[list, Eatery], *args, **kwargs): tzinfo=kwargs.get("tzinfo"), start=kwargs.get("start"), end=kwargs.get("end") - ) \ No newline at end of file + ) + + def description(self): + return "EateryToJson" diff --git a/api/dfg/InMemoryCache.py b/api/dfg/InMemoryCache.py index b6a089d..2ae6e1c 100644 --- a/api/dfg/InMemoryCache.py +++ b/api/dfg/InMemoryCache.py @@ -68,4 +68,8 @@ def to_json(self, *args, **kwargs): if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): return snapshot.to_json() return EateryToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) + + def description(self): + return "InMemoryCache" + diff --git a/api/dfg/LeftMergeById.py b/api/dfg/LeftMergeById.py index 96eb918..d748652 100644 --- a/api/dfg/LeftMergeById.py +++ b/api/dfg/LeftMergeById.py @@ -39,7 +39,7 @@ def __call__(self, *args, **kwargs): return merged_lst def description(self): - return "LeftMerge" + return "LeftMergeById" def _pop_first(lst: list): diff --git a/api/dfg/WaitTimeFilter.py b/api/dfg/WaitTimeFilter.py index 3e1b89b..4982747 100644 --- a/api/dfg/WaitTimeFilter.py +++ b/api/dfg/WaitTimeFilter.py @@ -31,4 +31,7 @@ def __call__(self, *args, **kwargs): eatery_clone = eatery.clone() eatery_clone.wait_times = wait_times_filtered result.append(eatery_clone) - return result \ No newline at end of file + return result + + def description(self): + return "WaitTimeFilter" diff --git a/api/dfg/macros/LeftMergeEateries.py b/api/dfg/macros/LeftMergeEateries.py index cc77855..c25ca34 100644 --- a/api/dfg/macros/LeftMergeEateries.py +++ b/api/dfg/macros/LeftMergeEateries.py @@ -16,4 +16,7 @@ def children(self): return self.macro.children() def __call__(self, *args, **kwargs): - return self.macro(*args, **kwargs) \ No newline at end of file + return self.macro(*args, **kwargs) + + def description(self): + return "LeftMergeEateries" \ No newline at end of file From 0259cd1b63e891a235494c44e37b6c29f468eec6 Mon Sep 17 00:00:00 2001 From: William Ma Date: Wed, 5 Jan 2022 17:13:04 -0500 Subject: [PATCH 026/305] style: Fix indentation --- api/datatype/Eatery.py | 32 +++++++++------ api/datatype/Event.py | 3 +- api/datatype/Menu.py | 1 + api/datatype/MenuCategory.py | 3 +- api/datatype/MenuItem.py | 2 +- api/datatype/WaitTime.py | 3 +- api/datatype/WaitTimesDay.py | 6 +-- api/dfg/CornellDiningNow.py | 16 ++++---- api/dfg/DfgNode.py | 2 +- api/dfg/EateryFromJson.py | 7 ++-- api/dfg/EateryStubs.py | 8 ++-- api/dfg/EateryToJson.py | 6 ++- api/dfg/ExternalEateries.py | 27 ++++--------- api/dfg/InMemoryCache.py | 10 ++--- api/dfg/LeftMergeById.py | 12 +++--- api/dfg/WaitTimeFilter.py | 9 ++++- api/dfg/WaitTimes.py | 61 +++++++++++++++++------------ api/dfg/macros/LeftMergeEateries.py | 4 +- api/views.py | 4 +- 19 files changed, 117 insertions(+), 99 deletions(-) diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index 15dbc61..adf9a8c 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -7,6 +7,7 @@ from api.datatype.Event import Event, filter_range from api.datatype.WaitTimesDay import WaitTimesDay + class EateryID(Enum): ONE_ZERO_FOUR_WEST = 1 LIBE_CAFE = 2 @@ -47,6 +48,7 @@ class EateryID(Enum): LOUIES = 37 ANABELS_GROCERY = 38 + class Eatery: def __init__( @@ -93,10 +95,12 @@ def to_json( "id": self.id.value, "name": self.name, "campus_area": self.campus_area, - "events": None if self.known_events is None else [event.to_json() for event in self.events(tzinfo, start, end)], + "events": None if self.known_events is None + else [event.to_json() for event in self.events(tzinfo, start, end)], "latitude": self.latitude, "longitude": self.longitude, - "payment_methods": None if self.payment_methods is None else [payment_method for payment_method in self.payment_methods], + "payment_methods": None if self.payment_methods is None + else [payment_method for payment_method in self.payment_methods], "location": self.location, "online_order": self.online_order, "online_order_url": self.online_order_url, @@ -107,18 +111,20 @@ def to_json( @staticmethod def from_json(eatery_json): return Eatery( - id = None if "id" not in eatery_json else EateryID(eatery_json["id"]), - name = None if "name" not in eatery_json else eatery_json["name"], + id=None if "id" not in eatery_json else EateryID(eatery_json["id"]), + name=None if "name" not in eatery_json else eatery_json["name"], campus_area=None if "campus_area" not in eatery_json else eatery_json["campus_area"], - events = None if "events" not in eatery_json or eatery_json["events"] is None else [Event.from_json(event) for event in eatery_json["events"]], - latitude = None if "latitude" not in eatery_json else eatery_json["latitude"], - longitude = None if "longitude" not in eatery_json else eatery_json["longitude"], - payment_methods = None if "payment_methods" not in eatery_json else eatery_json["payment_methods"], - location = None if "location" not in eatery_json else eatery_json["location"], - online_order = None if "online_order" not in eatery_json else eatery_json["online_order"], - online_order_url = None if "online_order_url" not in eatery_json else eatery_json["online_order_url"], - wait_times = None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]] + events=None if "events" not in eatery_json or eatery_json["events"] is None + else [Event.from_json(event) for event in eatery_json["events"]], + latitude=None if "latitude" not in eatery_json else eatery_json["latitude"], + longitude=None if "longitude" not in eatery_json else eatery_json["longitude"], + payment_methods=None if "payment_methods" not in eatery_json else eatery_json["payment_methods"], + location=None if "location" not in eatery_json else eatery_json["location"], + online_order=None if "online_order" not in eatery_json else eatery_json["online_order"], + online_order_url=None if "online_order_url" not in eatery_json else eatery_json["online_order_url"], + wait_times=None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None + else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]] ) def clone(self): - return Eatery.from_json(self.to_json()) \ No newline at end of file + return Eatery.from_json(self.to_json()) diff --git a/api/datatype/Event.py b/api/datatype/Event.py index f3a538f..8a19bd8 100644 --- a/api/datatype/Event.py +++ b/api/datatype/Event.py @@ -6,6 +6,7 @@ import pytz + class Event: def __init__( @@ -52,7 +53,7 @@ def _combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): if events is None: return [] - + if start is None and end is None: return events diff --git a/api/datatype/Menu.py b/api/datatype/Menu.py index c48e100..c7505b0 100644 --- a/api/datatype/Menu.py +++ b/api/datatype/Menu.py @@ -1,5 +1,6 @@ from api.datatype.MenuCategory import MenuCategory + class Menu: def __init__(self, categories: list[MenuCategory]): diff --git a/api/datatype/MenuCategory.py b/api/datatype/MenuCategory.py index b522954..f2cd1cc 100644 --- a/api/datatype/MenuCategory.py +++ b/api/datatype/MenuCategory.py @@ -1,5 +1,6 @@ from api.datatype.MenuItem import MenuItem + class MenuCategory: def __init__(self, category: str, items: list[MenuItem]): @@ -11,7 +12,7 @@ def to_json(self): "category": self.category, "items": [item.to_json() for item in self.items] } - + @staticmethod def from_json(category_json): return MenuCategory( diff --git a/api/datatype/MenuItem.py b/api/datatype/MenuItem.py index cfd7756..1111353 100644 --- a/api/datatype/MenuItem.py +++ b/api/datatype/MenuItem.py @@ -20,7 +20,7 @@ def to_json(self): "healthy": self.healthy, "name": self.name } - + @staticmethod def from_json(item_json): return MenuItem( diff --git a/api/datatype/WaitTime.py b/api/datatype/WaitTime.py index 9825c3b..7e5cfca 100644 --- a/api/datatype/WaitTime.py +++ b/api/datatype/WaitTime.py @@ -22,9 +22,8 @@ def to_json(self): @staticmethod def from_json(wait_time_json): return WaitTime( - timestamp = wait_time_json["timestamp"], + timestamp=wait_time_json["timestamp"], wait_time_low=wait_time_json["wait_time_low"], wait_time_expected=wait_time_json["wait_time_expected"], wait_time_high=wait_time_json["wait_time_high"] ) - diff --git a/api/datatype/WaitTimesDay.py b/api/datatype/WaitTimesDay.py index 2326b85..cb9ea86 100644 --- a/api/datatype/WaitTimesDay.py +++ b/api/datatype/WaitTimesDay.py @@ -1,8 +1,7 @@ - - from datetime import date from .WaitTime import WaitTime + class WaitTimesDay: def __init__( self, @@ -17,11 +16,10 @@ def to_json(self): "canonical_date": str(self.canonical_date), "data": [wait_time.to_json() for wait_time in self.data] } - + @staticmethod def from_json(wait_times_day_json): return WaitTimesDay( canonical_date=date.fromisoformat(wait_times_day_json["canonical_date"]), data=[WaitTime.from_json(wait_time) for wait_time in wait_times_day_json["data"]] ) - diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index dd07ac4..b802065 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -10,8 +10,8 @@ from datetime import date -class CornellDiningNow(DfgNode): +class CornellDiningNow(DfgNode): CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" def __call__(self, *args, **kwargs) -> list[Eatery]: @@ -45,8 +45,8 @@ def parse_eatery(json_eatery: dict) -> Eatery: longitude=json_eatery["longitude"], events=CornellDiningNow.eatery_events_from_json( json_operating_hours=json_eatery["operatingHours"], - json_dining_items = json_eatery["diningItems"], - is_cafe = is_cafe + json_dining_items=json_eatery["diningItems"], + is_cafe=is_cafe ), payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), location=json_eatery["location"], @@ -103,12 +103,12 @@ def cafe_menu_from_json(json_dining_items: list) -> Menu: for item in json_dining_items: if item['category'] not in category_map: category_map[item['category']] = [] - category_map[item['category']].append(MenuItem(healthy=item['healthy'], name = item['item'])) + category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) categories = [] for category_name in category_map: categories.append(MenuCategory(category_name, category_map[category_name])) return Menu(categories=categories) - + @staticmethod def dining_hall_menu_from_json(json_menu: list) -> Menu: json_menu = sorted( @@ -173,13 +173,13 @@ def dining_id_to_internal_id(id: int): elif id == 29: return EateryID.KEETON_HOUSE elif id == 42: - return EateryID.MANN_CAFE + return EateryID.MANN_CAFE elif id == 18: return EateryID.MARTHAS_CAFE elif id == 19: return EateryID.MATTINS_CAFE elif id == 33: - return EateryID.MCCORMICKS + return EateryID.MCCORMICKS elif id == 3: return EateryID.NORTH_STAR_DINING elif id == 20: @@ -187,7 +187,7 @@ def dining_id_to_internal_id(id: int): elif id == 4: return EateryID.RISLEY elif id == 5: - return EateryID.RPCC + return EateryID.RPCC elif id == 30: return EateryID.ROSE_HOUSE elif id == 21: diff --git a/api/dfg/DfgNode.py b/api/dfg/DfgNode.py index 373d86a..57d5821 100644 --- a/api/dfg/DfgNode.py +++ b/api/dfg/DfgNode.py @@ -4,7 +4,7 @@ def __call__(self, *args, **kwargs): raise Exception() def children(self): - return [] + return [] def description(self): raise Exception() diff --git a/api/dfg/EateryFromJson.py b/api/dfg/EateryFromJson.py index 53bbf4e..8466771 100644 --- a/api/dfg/EateryFromJson.py +++ b/api/dfg/EateryFromJson.py @@ -3,6 +3,7 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery + class EateryFromJson(DfgNode): def __init__(self, child: DfgNode): @@ -14,7 +15,7 @@ def __call__(self, *args, **kwargs): def children(self): return [self.child] - + @staticmethod def from_json(obj: Union[list, dict], *args, **kwargs): if isinstance(obj, list): @@ -24,6 +25,6 @@ def from_json(obj: Union[list, dict], *args, **kwargs): ] else: return Eatery.from_json(obj) - + def description(self): - return "EateryFromJson" \ No newline at end of file + return "EateryFromJson" diff --git a/api/dfg/EateryStubs.py b/api/dfg/EateryStubs.py index 36e0e86..4b1d4c6 100644 --- a/api/dfg/EateryStubs.py +++ b/api/dfg/EateryStubs.py @@ -1,11 +1,11 @@ - from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID + class EateryStubs(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: - return [Eatery(id = id) for id in EateryID] - + return [Eatery(id=id) for id in EateryID] + def description(self): - return "EateryStubs" \ No newline at end of file + return "EateryStubs" diff --git a/api/dfg/EateryToJson.py b/api/dfg/EateryToJson.py index 574d1f7..51889ba 100644 --- a/api/dfg/EateryToJson.py +++ b/api/dfg/EateryToJson.py @@ -2,6 +2,8 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery + + class EateryToJson(DfgNode): def __init__(self, child: DfgNode): @@ -13,7 +15,7 @@ def __call__(self, *args, **kwargs): def children(self): return [self.child] - + @staticmethod def to_json(obj: Union[list, Eatery], *args, **kwargs): if isinstance(obj, list): @@ -27,6 +29,6 @@ def to_json(obj: Union[list, Eatery], *args, **kwargs): start=kwargs.get("start"), end=kwargs.get("end") ) - + def description(self): return "EateryToJson" diff --git a/api/dfg/ExternalEateries.py b/api/dfg/ExternalEateries.py index fe6172c..edf9957 100644 --- a/api/dfg/ExternalEateries.py +++ b/api/dfg/ExternalEateries.py @@ -1,23 +1,22 @@ import datetime +import json import re -from typing import Union import pytz -from api.dfg.DfgNode import DfgNode - from api.datatype.Eatery import Eatery, EateryID from api.datatype.Event import Event from api.datatype.Menu import Menu from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem +from api.dfg.DfgNode import DfgNode -import json # eventually need to deprecate this for a custom DB backend storing all of the overrides class ExternalEateries(DfgNode): - # TODO: Make parsing of ExternalEateries the same as parsing of normal eateries, except from file, and then read external data on top of this + # TODO: Make parsing of ExternalEateries the same as parsing of normal eateries, except from file, and then read + # external data on top of this EXTERNAL_EATERIES_PATH = "static_sources/external_eateries.json" # based on date.weekday() @@ -52,13 +51,13 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: @staticmethod def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date) -> Eatery: return Eatery( - id = EateryID(json_eatery["id"]), + id=EateryID(json_eatery["id"]), name=json_eatery["name"], campus_area=json_eatery["campusArea"]["descrshort"], events=ExternalEateries.eatery_events_from_json( json_operating_hours=json_eatery["operatingHours"], json_dates_closed=json_eatery["datesClosed"], - json_dining_items = json_eatery["diningItems"], + json_dining_items=json_eatery["diningItems"], start_date=start, end_date=end, ), @@ -70,7 +69,6 @@ def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date online_order_url=json_eatery["onlineOrderUrl"] ) - @staticmethod def generate_payment_methods(json_paymethods: list): payment_methods = [] @@ -84,7 +82,6 @@ def generate_payment_methods(json_paymethods: list): if takes_swipes: payment_methods.append("swipes") return payment_methods - @staticmethod def eatery_events_from_json( @@ -148,7 +145,7 @@ def time_since_midnight(time_str: str) -> datetime.time: if not match: return datetime.time() - hours = int(match.group(1))%12 + hours = int(match.group(1)) % 12 minutes = int(match.group(2)) is_pm = match.group(3) == "pm" return datetime.time( @@ -162,7 +159,7 @@ def eatery_menu_from_json(json_dining_items: list) -> Menu: for item in json_dining_items: if item['category'] not in category_map: category_map[item['category']] = [] - category_map[item['category']].append(MenuItem(healthy=item['healthy'], name = item['item'])) + category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) categories = [] for category_name in category_map: categories.append(MenuCategory(category_name, category_map[category_name])) @@ -180,11 +177,3 @@ def timestamp_combined(date: datetime.date, time: datetime.time): def description(self): return "ExternalEateries" - - -if __name__ == "__main__": - from api.dfg.EateryToJson import EateryToJson - import json - - dfg = EateryToJson(ExternalEateries()) - print(json.dumps(dfg(), indent=2)) diff --git a/api/dfg/InMemoryCache.py b/api/dfg/InMemoryCache.py index 2ae6e1c..3c1bd0e 100644 --- a/api/dfg/InMemoryCache.py +++ b/api/dfg/InMemoryCache.py @@ -6,7 +6,8 @@ from api.dfg.EateryToJson import EateryToJson -class DataSnapshot(): +class DataSnapshot: + def __init__(self, args, kwargs, data, time): self.data = data self.recorded_time = time @@ -25,6 +26,7 @@ def to_json(self): def get_recorded_time(self): return self.recorded_time + class InMemoryCache(DfgNode): def __init__(self, child, expiration: float = 3600, max_size: int = 5): @@ -32,7 +34,7 @@ def __init__(self, child, expiration: float = 3600, max_size: int = 5): self.expiration = expiration self.max_size = max_size self.snapshots: list[DataSnapshot] = [] - + def current_time(self): return time.time() @@ -68,8 +70,6 @@ def to_json(self, *args, **kwargs): if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): return snapshot.to_json() return EateryToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) - + def description(self): return "InMemoryCache" - - diff --git a/api/dfg/LeftMergeById.py b/api/dfg/LeftMergeById.py index d748652..84cd8c3 100644 --- a/api/dfg/LeftMergeById.py +++ b/api/dfg/LeftMergeById.py @@ -1,6 +1,8 @@ from api.dfg.DfgNode import DfgNode -# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if conflict) + +# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if +# conflict) class LeftMergeById(DfgNode): def __init__(self, left: DfgNode, right: DfgNode): @@ -16,14 +18,14 @@ def __call__(self, *args, **kwargs): left_json = _pop_first(left_lst) right_json = _pop_first(right_lst) merged_lst = [] - while left_json != None and right_json != None: + while left_json is not None and right_json is not None: if left_json["id"] == right_json["id"]: merged_json = {} for key in right_json: - if right_json[key] != None: + if right_json[key] is not None: merged_json[key] = right_json[key] for key in left_json: - if left_json[key] != None: + if left_json[key] is not None: merged_json[key] = left_json[key] merged_lst.append(merged_json) left_json = _pop_first(left_lst) @@ -46,4 +48,4 @@ def _pop_first(lst: list): try: return lst.pop(0) except IndexError: - return None \ No newline at end of file + return None diff --git a/api/dfg/WaitTimeFilter.py b/api/dfg/WaitTimeFilter.py index 4982747..f88c8de 100644 --- a/api/dfg/WaitTimeFilter.py +++ b/api/dfg/WaitTimeFilter.py @@ -1,6 +1,7 @@ from api.datatype.WaitTimesDay import WaitTimesDay from api.dfg.DfgNode import DfgNode + # Removes all wait times that are not part of the eatery's events class WaitTimeFilter(DfgNode): @@ -21,7 +22,11 @@ def __call__(self, *args, **kwargs): for day_wait_times in eatery.wait_times: filtered_data = [] for wait_time_data in day_wait_times.data: - eatery_events = eatery.events(tzinfo=kwargs.get("tzinfo"), start=kwargs.get("start"), end=kwargs.get("end")) + eatery_events = eatery.events( + tzinfo=kwargs.get("tzinfo"), + start=kwargs.get("start"), + end=kwargs.get("end") + ) if any([wait_time_data.timestamp in event for event in eatery_events]): filtered_data.append(wait_time_data) wait_times_filtered.append(WaitTimesDay( @@ -32,6 +37,6 @@ def __call__(self, *args, **kwargs): eatery_clone.wait_times = wait_times_filtered result.append(eatery_clone) return result - + def description(self): return "WaitTimeFilter" diff --git a/api/dfg/WaitTimes.py b/api/dfg/WaitTimes.py index e58540e..9b3ea46 100644 --- a/api/dfg/WaitTimes.py +++ b/api/dfg/WaitTimes.py @@ -1,29 +1,30 @@ -from typing import Mapping -from django.db.models import Avg from datetime import date, datetime, timedelta + import pytz +from django.db.models import Avg from api.datatype.Eatery import Eatery, EateryID -from api.datatype.WaitTimesDay import WaitTimesDay from api.datatype.WaitTime import WaitTime - +from api.datatype.WaitTimesDay import WaitTimesDay from api.dfg.DfgNode import DfgNode from transactions.models import TransactionHistory from transactions.serializers import TransactionHistorySerializer + class WaitTimes(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: transactions_by_date = {} past_days = [] date = kwargs.get("start") - eatery_ids = set() + eatery_ids: set[EateryID] = set() while date <= kwargs.get("end"): # We only calculate the wait times for this first day transactions_on_date = {} for i in range(1, 13): - # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the transaction_count - past_day = date - timedelta(days = 7 * i) + # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the + # transaction_count + past_day = date - timedelta(days=7 * i) past_days.append(past_day) transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ .values("eatery_id", "block_end_time") \ @@ -38,17 +39,21 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: transactions_on_date[eatery_id].append(transaction_history) transactions_by_date[date] = transactions_on_date date += timedelta(days=1) - + eateries = [] for eatery_id in eatery_ids: eatery_wait_times_by_day = [] for date in transactions_by_date: if eatery_id in transactions_by_date[date]: - eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day(eatery_id, date, transactions_by_date[date][eatery_id])) + eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day( + eatery_id, + date, + transactions_by_date[date][eatery_id] + )) eateries.append( Eatery( - id=eatery_id, - wait_times = eatery_wait_times_by_day + id=eatery_id, + wait_times=eatery_wait_times_by_day ) ) return eateries @@ -56,7 +61,7 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: # Expected amount of time (in seconds) for the length of the line to decrease by 1 person # Returns [lower, expected, upper] @staticmethod - def line_decrease_by_one_time(eatery_id: EateryID) -> float: + def line_decrease_by_one_time(eatery_id: EateryID) -> list[int]: if eatery_id == EateryID.MACS_CAFE: return [24, 27, 30] elif eatery_id == EateryID.MATTINS_CAFE: @@ -67,11 +72,11 @@ def line_decrease_by_one_time(eatery_id: EateryID) -> float: return [4, 8, 12] else: return [18, 21, 24] - - # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the amount of time to check out - # Returns [lower, expected, upper] + + # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the + # amount of time to check out Returns [lower, expected, upper] @staticmethod - def base_time_to_get_food(eatery_id: int) -> float: + def base_time_to_get_food(eatery_id: EateryID) -> list[int]: if eatery_id == EateryID.MACS_CAFE: return [240, 300, 360] elif eatery_id == EateryID.MATTINS_CAFE: @@ -85,9 +90,9 @@ def base_time_to_get_food(eatery_id: int) -> float: @staticmethod def generate_eatery_wait_times_by_day( - eatery_id: int, - date: date, - transactions: list[TransactionHistorySerializer] + eatery_id: EateryID, + date: date, + transactions: list[TransactionHistorySerializer] ) -> WaitTimesDay: wait_times_data = [] customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] @@ -95,7 +100,10 @@ def generate_eatery_wait_times_by_day( base_times = WaitTimes.base_time_to_get_food(eatery_id) line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago - how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] + how_long_ago_guest_arrival = ( + base_times[1] + + line_decrease_times[1] * transactions[index].data["transaction_avg"] + ) prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) if prev_bucket_guest_arrival > 9: # TODO: Send a slack error here instead @@ -111,12 +119,13 @@ def generate_eatery_wait_times_by_day( block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() timestamp = int(WaitTimes.timestamp_combined(date, block_end_time) - 5 * 60 / 2) wait_times_data.insert(0, WaitTime( - timestamp=timestamp, - wait_time_low = wait_time_low, - wait_time_expected= wait_time_expected, - wait_time_high = wait_time_high)) - - return WaitTimesDay(canonical_date = date, data = wait_times_data) + timestamp=timestamp, + wait_time_low=wait_time_low, + wait_time_expected=wait_time_expected, + wait_time_high=wait_time_high + )) + + return WaitTimesDay(canonical_date=date, data=wait_times_data) @staticmethod def timestamp_combined(date: datetime.date, time: datetime.time): diff --git a/api/dfg/macros/LeftMergeEateries.py b/api/dfg/macros/LeftMergeEateries.py index c25ca34..4fdaef9 100644 --- a/api/dfg/macros/LeftMergeEateries.py +++ b/api/dfg/macros/LeftMergeEateries.py @@ -3,7 +3,9 @@ from api.dfg.EateryFromJson import EateryFromJson from api.dfg.LeftMergeById import LeftMergeById + class LeftMergeEateries(DfgNode): + def __init__(self, left: DfgNode, right: DfgNode): self.macro = EateryFromJson( LeftMergeById( @@ -19,4 +21,4 @@ def __call__(self, *args, **kwargs): return self.macro(*args, **kwargs) def description(self): - return "LeftMergeEateries" \ No newline at end of file + return "LeftMergeEateries" diff --git a/api/views.py b/api/views.py index 3a4f446..e9345ed 100644 --- a/api/views.py +++ b/api/views.py @@ -34,6 +34,7 @@ re_raise_exceptions=True ) + def index(request): tzinfo = pytz.timezone("US/Eastern") reload = request.GET.get('reload') @@ -45,6 +46,7 @@ def index(request): ) return JsonResponse(result) + def google_sheets_eateries(request): # dfg = DictResponseWrapper( # EateryToJson( @@ -60,4 +62,4 @@ def google_sheets_eateries(request): # end=date.today() + timedelta(days=7) # ) - return JsonResponse([]) \ No newline at end of file + return JsonResponse([]) From ba65b750c6614f7a34b1af8a64a237cac0541b29 Mon Sep 17 00:00:00 2001 From: William Ma Date: Wed, 5 Jan 2022 17:13:53 -0500 Subject: [PATCH 027/305] cornell dining now: Fix Rusty's and Trillium ID's --- api/dfg/CornellDiningNow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index b802065..6a5bdf4 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -191,11 +191,11 @@ def dining_id_to_internal_id(id: int): elif id == 30: return EateryID.ROSE_HOUSE elif id == 21: - return EateryID.ROSE_HOUSE + return EateryID.RUSTYS elif id == 13: return EateryID.STRAIGHT_FROM_THE_MARKET elif id == 23: - return EateryID.TERRACE + return EateryID.TRILLIUM else: return None From a60d808058923263d211295e4541488b7bce5eaf Mon Sep 17 00:00:00 2001 From: William Ma Date: Wed, 5 Jan 2022 17:16:57 -0500 Subject: [PATCH 028/305] style: Fix over-indented arguments --- api/dfg/WaitTimes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/dfg/WaitTimes.py b/api/dfg/WaitTimes.py index 9b3ea46..a4c54e5 100644 --- a/api/dfg/WaitTimes.py +++ b/api/dfg/WaitTimes.py @@ -90,10 +90,11 @@ def base_time_to_get_food(eatery_id: EateryID) -> list[int]: @staticmethod def generate_eatery_wait_times_by_day( - eatery_id: EateryID, - date: date, - transactions: list[TransactionHistorySerializer] + eatery_id: EateryID, + date: date, + transactions: list[TransactionHistorySerializer] ) -> WaitTimesDay: + wait_times_data = [] customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] for index in reversed(range(0, len(transactions))): From 1b47518ef561287c67ce0a21a3772cdf2b4f2bb1 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 6 Jan 2022 07:47:23 -0500 Subject: [PATCH 029/305] Return correct wait time format even if no data --- api/dfg/WaitTimes.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/api/dfg/WaitTimes.py b/api/dfg/WaitTimes.py index e58540e..c16c5ad 100644 --- a/api/dfg/WaitTimes.py +++ b/api/dfg/WaitTimes.py @@ -15,12 +15,11 @@ class WaitTimes(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: transactions_by_date = {} - past_days = [] date = kwargs.get("start") - eatery_ids = set() while date <= kwargs.get("end"): # We only calculate the wait times for this first day transactions_on_date = {} + past_days = [] for i in range(1, 13): # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the transaction_count past_day = date - timedelta(days = 7 * i) @@ -32,19 +31,20 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: transaction_history = TransactionHistorySerializer(unit) if transaction_history.data['eatery_id'] != 0: eatery_id = EateryID(transaction_history.data['eatery_id']) - eatery_ids.add(eatery_id) if eatery_id not in transactions_on_date: transactions_on_date[eatery_id] = [] transactions_on_date[eatery_id].append(transaction_history) transactions_by_date[date] = transactions_on_date date += timedelta(days=1) - + eateries = [] - for eatery_id in eatery_ids: + for eatery_id in EateryID: eatery_wait_times_by_day = [] for date in transactions_by_date: + transactions = [] if eatery_id in transactions_by_date[date]: - eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day(eatery_id, date, transactions_by_date[date][eatery_id])) + transactions = transactions_by_date[date][eatery_id] + eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day(eatery_id, date, transactions)) eateries.append( Eatery( id=eatery_id, @@ -71,7 +71,7 @@ def line_decrease_by_one_time(eatery_id: EateryID) -> float: # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the amount of time to check out # Returns [lower, expected, upper] @staticmethod - def base_time_to_get_food(eatery_id: int) -> float: + def base_time_to_get_food(eatery_id: EateryID) -> float: if eatery_id == EateryID.MACS_CAFE: return [240, 300, 360] elif eatery_id == EateryID.MATTINS_CAFE: @@ -85,7 +85,7 @@ def base_time_to_get_food(eatery_id: int) -> float: @staticmethod def generate_eatery_wait_times_by_day( - eatery_id: int, + eatery_id: EateryID, date: date, transactions: list[TransactionHistorySerializer] ) -> WaitTimesDay: @@ -115,7 +115,6 @@ def generate_eatery_wait_times_by_day( wait_time_low = wait_time_low, wait_time_expected= wait_time_expected, wait_time_high = wait_time_high)) - return WaitTimesDay(canonical_date = date, data = wait_times_data) @staticmethod From c691ce3fc1013a85a158872ff78f61553327418b Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 6 Jan 2022 09:52:15 -0500 Subject: [PATCH 030/305] Eateries schema --- api/datatype/Eatery.py | 32 +++++---- api/datatype/Event.py | 3 +- api/datatype/Menu.py | 1 + api/datatype/MenuCategory.py | 3 +- api/datatype/MenuItem.py | 2 +- api/datatype/WaitTime.py | 3 +- api/datatype/WaitTimesDay.py | 6 +- api/dfg/CornellDiningNow.py | 20 +++--- api/dfg/DfgNode.py | 2 +- api/dfg/EateryFromJson.py | 7 +- api/dfg/EateryStubs.py | 8 +-- api/dfg/EateryToJson.py | 6 +- api/dfg/ExternalEateries.py | 27 +++---- api/dfg/InMemoryCache.py | 10 +-- api/dfg/LeftMergeById.py | 12 ++-- api/dfg/WaitTimeFilter.py | 9 ++- api/dfg/WaitTimes.py | 56 +++++++++------ api/dfg/macros/LeftMergeEateries.py | 4 +- api/views.py | 4 +- eateries/migrations/0001_initial.py | 105 ++++++++++++++++++++++++++++ eateries/models.py | 93 +++++++++++++++++++++--- 21 files changed, 306 insertions(+), 107 deletions(-) create mode 100644 eateries/migrations/0001_initial.py diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index 15dbc61..adf9a8c 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -7,6 +7,7 @@ from api.datatype.Event import Event, filter_range from api.datatype.WaitTimesDay import WaitTimesDay + class EateryID(Enum): ONE_ZERO_FOUR_WEST = 1 LIBE_CAFE = 2 @@ -47,6 +48,7 @@ class EateryID(Enum): LOUIES = 37 ANABELS_GROCERY = 38 + class Eatery: def __init__( @@ -93,10 +95,12 @@ def to_json( "id": self.id.value, "name": self.name, "campus_area": self.campus_area, - "events": None if self.known_events is None else [event.to_json() for event in self.events(tzinfo, start, end)], + "events": None if self.known_events is None + else [event.to_json() for event in self.events(tzinfo, start, end)], "latitude": self.latitude, "longitude": self.longitude, - "payment_methods": None if self.payment_methods is None else [payment_method for payment_method in self.payment_methods], + "payment_methods": None if self.payment_methods is None + else [payment_method for payment_method in self.payment_methods], "location": self.location, "online_order": self.online_order, "online_order_url": self.online_order_url, @@ -107,18 +111,20 @@ def to_json( @staticmethod def from_json(eatery_json): return Eatery( - id = None if "id" not in eatery_json else EateryID(eatery_json["id"]), - name = None if "name" not in eatery_json else eatery_json["name"], + id=None if "id" not in eatery_json else EateryID(eatery_json["id"]), + name=None if "name" not in eatery_json else eatery_json["name"], campus_area=None if "campus_area" not in eatery_json else eatery_json["campus_area"], - events = None if "events" not in eatery_json or eatery_json["events"] is None else [Event.from_json(event) for event in eatery_json["events"]], - latitude = None if "latitude" not in eatery_json else eatery_json["latitude"], - longitude = None if "longitude" not in eatery_json else eatery_json["longitude"], - payment_methods = None if "payment_methods" not in eatery_json else eatery_json["payment_methods"], - location = None if "location" not in eatery_json else eatery_json["location"], - online_order = None if "online_order" not in eatery_json else eatery_json["online_order"], - online_order_url = None if "online_order_url" not in eatery_json else eatery_json["online_order_url"], - wait_times = None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]] + events=None if "events" not in eatery_json or eatery_json["events"] is None + else [Event.from_json(event) for event in eatery_json["events"]], + latitude=None if "latitude" not in eatery_json else eatery_json["latitude"], + longitude=None if "longitude" not in eatery_json else eatery_json["longitude"], + payment_methods=None if "payment_methods" not in eatery_json else eatery_json["payment_methods"], + location=None if "location" not in eatery_json else eatery_json["location"], + online_order=None if "online_order" not in eatery_json else eatery_json["online_order"], + online_order_url=None if "online_order_url" not in eatery_json else eatery_json["online_order_url"], + wait_times=None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None + else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]] ) def clone(self): - return Eatery.from_json(self.to_json()) \ No newline at end of file + return Eatery.from_json(self.to_json()) diff --git a/api/datatype/Event.py b/api/datatype/Event.py index f3a538f..8a19bd8 100644 --- a/api/datatype/Event.py +++ b/api/datatype/Event.py @@ -6,6 +6,7 @@ import pytz + class Event: def __init__( @@ -52,7 +53,7 @@ def _combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): if events is None: return [] - + if start is None and end is None: return events diff --git a/api/datatype/Menu.py b/api/datatype/Menu.py index c48e100..c7505b0 100644 --- a/api/datatype/Menu.py +++ b/api/datatype/Menu.py @@ -1,5 +1,6 @@ from api.datatype.MenuCategory import MenuCategory + class Menu: def __init__(self, categories: list[MenuCategory]): diff --git a/api/datatype/MenuCategory.py b/api/datatype/MenuCategory.py index b522954..f2cd1cc 100644 --- a/api/datatype/MenuCategory.py +++ b/api/datatype/MenuCategory.py @@ -1,5 +1,6 @@ from api.datatype.MenuItem import MenuItem + class MenuCategory: def __init__(self, category: str, items: list[MenuItem]): @@ -11,7 +12,7 @@ def to_json(self): "category": self.category, "items": [item.to_json() for item in self.items] } - + @staticmethod def from_json(category_json): return MenuCategory( diff --git a/api/datatype/MenuItem.py b/api/datatype/MenuItem.py index cfd7756..1111353 100644 --- a/api/datatype/MenuItem.py +++ b/api/datatype/MenuItem.py @@ -20,7 +20,7 @@ def to_json(self): "healthy": self.healthy, "name": self.name } - + @staticmethod def from_json(item_json): return MenuItem( diff --git a/api/datatype/WaitTime.py b/api/datatype/WaitTime.py index 9825c3b..7e5cfca 100644 --- a/api/datatype/WaitTime.py +++ b/api/datatype/WaitTime.py @@ -22,9 +22,8 @@ def to_json(self): @staticmethod def from_json(wait_time_json): return WaitTime( - timestamp = wait_time_json["timestamp"], + timestamp=wait_time_json["timestamp"], wait_time_low=wait_time_json["wait_time_low"], wait_time_expected=wait_time_json["wait_time_expected"], wait_time_high=wait_time_json["wait_time_high"] ) - diff --git a/api/datatype/WaitTimesDay.py b/api/datatype/WaitTimesDay.py index 2326b85..cb9ea86 100644 --- a/api/datatype/WaitTimesDay.py +++ b/api/datatype/WaitTimesDay.py @@ -1,8 +1,7 @@ - - from datetime import date from .WaitTime import WaitTime + class WaitTimesDay: def __init__( self, @@ -17,11 +16,10 @@ def to_json(self): "canonical_date": str(self.canonical_date), "data": [wait_time.to_json() for wait_time in self.data] } - + @staticmethod def from_json(wait_times_day_json): return WaitTimesDay( canonical_date=date.fromisoformat(wait_times_day_json["canonical_date"]), data=[WaitTime.from_json(wait_time) for wait_time in wait_times_day_json["data"]] ) - diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index dd07ac4..6a5bdf4 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -10,8 +10,8 @@ from datetime import date -class CornellDiningNow(DfgNode): +class CornellDiningNow(DfgNode): CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" def __call__(self, *args, **kwargs) -> list[Eatery]: @@ -45,8 +45,8 @@ def parse_eatery(json_eatery: dict) -> Eatery: longitude=json_eatery["longitude"], events=CornellDiningNow.eatery_events_from_json( json_operating_hours=json_eatery["operatingHours"], - json_dining_items = json_eatery["diningItems"], - is_cafe = is_cafe + json_dining_items=json_eatery["diningItems"], + is_cafe=is_cafe ), payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), location=json_eatery["location"], @@ -103,12 +103,12 @@ def cafe_menu_from_json(json_dining_items: list) -> Menu: for item in json_dining_items: if item['category'] not in category_map: category_map[item['category']] = [] - category_map[item['category']].append(MenuItem(healthy=item['healthy'], name = item['item'])) + category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) categories = [] for category_name in category_map: categories.append(MenuCategory(category_name, category_map[category_name])) return Menu(categories=categories) - + @staticmethod def dining_hall_menu_from_json(json_menu: list) -> Menu: json_menu = sorted( @@ -173,13 +173,13 @@ def dining_id_to_internal_id(id: int): elif id == 29: return EateryID.KEETON_HOUSE elif id == 42: - return EateryID.MANN_CAFE + return EateryID.MANN_CAFE elif id == 18: return EateryID.MARTHAS_CAFE elif id == 19: return EateryID.MATTINS_CAFE elif id == 33: - return EateryID.MCCORMICKS + return EateryID.MCCORMICKS elif id == 3: return EateryID.NORTH_STAR_DINING elif id == 20: @@ -187,15 +187,15 @@ def dining_id_to_internal_id(id: int): elif id == 4: return EateryID.RISLEY elif id == 5: - return EateryID.RPCC + return EateryID.RPCC elif id == 30: return EateryID.ROSE_HOUSE elif id == 21: - return EateryID.ROSE_HOUSE + return EateryID.RUSTYS elif id == 13: return EateryID.STRAIGHT_FROM_THE_MARKET elif id == 23: - return EateryID.TERRACE + return EateryID.TRILLIUM else: return None diff --git a/api/dfg/DfgNode.py b/api/dfg/DfgNode.py index 373d86a..57d5821 100644 --- a/api/dfg/DfgNode.py +++ b/api/dfg/DfgNode.py @@ -4,7 +4,7 @@ def __call__(self, *args, **kwargs): raise Exception() def children(self): - return [] + return [] def description(self): raise Exception() diff --git a/api/dfg/EateryFromJson.py b/api/dfg/EateryFromJson.py index 53bbf4e..8466771 100644 --- a/api/dfg/EateryFromJson.py +++ b/api/dfg/EateryFromJson.py @@ -3,6 +3,7 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery + class EateryFromJson(DfgNode): def __init__(self, child: DfgNode): @@ -14,7 +15,7 @@ def __call__(self, *args, **kwargs): def children(self): return [self.child] - + @staticmethod def from_json(obj: Union[list, dict], *args, **kwargs): if isinstance(obj, list): @@ -24,6 +25,6 @@ def from_json(obj: Union[list, dict], *args, **kwargs): ] else: return Eatery.from_json(obj) - + def description(self): - return "EateryFromJson" \ No newline at end of file + return "EateryFromJson" diff --git a/api/dfg/EateryStubs.py b/api/dfg/EateryStubs.py index 36e0e86..4b1d4c6 100644 --- a/api/dfg/EateryStubs.py +++ b/api/dfg/EateryStubs.py @@ -1,11 +1,11 @@ - from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID + class EateryStubs(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: - return [Eatery(id = id) for id in EateryID] - + return [Eatery(id=id) for id in EateryID] + def description(self): - return "EateryStubs" \ No newline at end of file + return "EateryStubs" diff --git a/api/dfg/EateryToJson.py b/api/dfg/EateryToJson.py index 574d1f7..51889ba 100644 --- a/api/dfg/EateryToJson.py +++ b/api/dfg/EateryToJson.py @@ -2,6 +2,8 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery + + class EateryToJson(DfgNode): def __init__(self, child: DfgNode): @@ -13,7 +15,7 @@ def __call__(self, *args, **kwargs): def children(self): return [self.child] - + @staticmethod def to_json(obj: Union[list, Eatery], *args, **kwargs): if isinstance(obj, list): @@ -27,6 +29,6 @@ def to_json(obj: Union[list, Eatery], *args, **kwargs): start=kwargs.get("start"), end=kwargs.get("end") ) - + def description(self): return "EateryToJson" diff --git a/api/dfg/ExternalEateries.py b/api/dfg/ExternalEateries.py index fe6172c..edf9957 100644 --- a/api/dfg/ExternalEateries.py +++ b/api/dfg/ExternalEateries.py @@ -1,23 +1,22 @@ import datetime +import json import re -from typing import Union import pytz -from api.dfg.DfgNode import DfgNode - from api.datatype.Eatery import Eatery, EateryID from api.datatype.Event import Event from api.datatype.Menu import Menu from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem +from api.dfg.DfgNode import DfgNode -import json # eventually need to deprecate this for a custom DB backend storing all of the overrides class ExternalEateries(DfgNode): - # TODO: Make parsing of ExternalEateries the same as parsing of normal eateries, except from file, and then read external data on top of this + # TODO: Make parsing of ExternalEateries the same as parsing of normal eateries, except from file, and then read + # external data on top of this EXTERNAL_EATERIES_PATH = "static_sources/external_eateries.json" # based on date.weekday() @@ -52,13 +51,13 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: @staticmethod def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date) -> Eatery: return Eatery( - id = EateryID(json_eatery["id"]), + id=EateryID(json_eatery["id"]), name=json_eatery["name"], campus_area=json_eatery["campusArea"]["descrshort"], events=ExternalEateries.eatery_events_from_json( json_operating_hours=json_eatery["operatingHours"], json_dates_closed=json_eatery["datesClosed"], - json_dining_items = json_eatery["diningItems"], + json_dining_items=json_eatery["diningItems"], start_date=start, end_date=end, ), @@ -70,7 +69,6 @@ def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date online_order_url=json_eatery["onlineOrderUrl"] ) - @staticmethod def generate_payment_methods(json_paymethods: list): payment_methods = [] @@ -84,7 +82,6 @@ def generate_payment_methods(json_paymethods: list): if takes_swipes: payment_methods.append("swipes") return payment_methods - @staticmethod def eatery_events_from_json( @@ -148,7 +145,7 @@ def time_since_midnight(time_str: str) -> datetime.time: if not match: return datetime.time() - hours = int(match.group(1))%12 + hours = int(match.group(1)) % 12 minutes = int(match.group(2)) is_pm = match.group(3) == "pm" return datetime.time( @@ -162,7 +159,7 @@ def eatery_menu_from_json(json_dining_items: list) -> Menu: for item in json_dining_items: if item['category'] not in category_map: category_map[item['category']] = [] - category_map[item['category']].append(MenuItem(healthy=item['healthy'], name = item['item'])) + category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) categories = [] for category_name in category_map: categories.append(MenuCategory(category_name, category_map[category_name])) @@ -180,11 +177,3 @@ def timestamp_combined(date: datetime.date, time: datetime.time): def description(self): return "ExternalEateries" - - -if __name__ == "__main__": - from api.dfg.EateryToJson import EateryToJson - import json - - dfg = EateryToJson(ExternalEateries()) - print(json.dumps(dfg(), indent=2)) diff --git a/api/dfg/InMemoryCache.py b/api/dfg/InMemoryCache.py index 2ae6e1c..3c1bd0e 100644 --- a/api/dfg/InMemoryCache.py +++ b/api/dfg/InMemoryCache.py @@ -6,7 +6,8 @@ from api.dfg.EateryToJson import EateryToJson -class DataSnapshot(): +class DataSnapshot: + def __init__(self, args, kwargs, data, time): self.data = data self.recorded_time = time @@ -25,6 +26,7 @@ def to_json(self): def get_recorded_time(self): return self.recorded_time + class InMemoryCache(DfgNode): def __init__(self, child, expiration: float = 3600, max_size: int = 5): @@ -32,7 +34,7 @@ def __init__(self, child, expiration: float = 3600, max_size: int = 5): self.expiration = expiration self.max_size = max_size self.snapshots: list[DataSnapshot] = [] - + def current_time(self): return time.time() @@ -68,8 +70,6 @@ def to_json(self, *args, **kwargs): if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): return snapshot.to_json() return EateryToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) - + def description(self): return "InMemoryCache" - - diff --git a/api/dfg/LeftMergeById.py b/api/dfg/LeftMergeById.py index d748652..84cd8c3 100644 --- a/api/dfg/LeftMergeById.py +++ b/api/dfg/LeftMergeById.py @@ -1,6 +1,8 @@ from api.dfg.DfgNode import DfgNode -# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if conflict) + +# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if +# conflict) class LeftMergeById(DfgNode): def __init__(self, left: DfgNode, right: DfgNode): @@ -16,14 +18,14 @@ def __call__(self, *args, **kwargs): left_json = _pop_first(left_lst) right_json = _pop_first(right_lst) merged_lst = [] - while left_json != None and right_json != None: + while left_json is not None and right_json is not None: if left_json["id"] == right_json["id"]: merged_json = {} for key in right_json: - if right_json[key] != None: + if right_json[key] is not None: merged_json[key] = right_json[key] for key in left_json: - if left_json[key] != None: + if left_json[key] is not None: merged_json[key] = left_json[key] merged_lst.append(merged_json) left_json = _pop_first(left_lst) @@ -46,4 +48,4 @@ def _pop_first(lst: list): try: return lst.pop(0) except IndexError: - return None \ No newline at end of file + return None diff --git a/api/dfg/WaitTimeFilter.py b/api/dfg/WaitTimeFilter.py index 4982747..f88c8de 100644 --- a/api/dfg/WaitTimeFilter.py +++ b/api/dfg/WaitTimeFilter.py @@ -1,6 +1,7 @@ from api.datatype.WaitTimesDay import WaitTimesDay from api.dfg.DfgNode import DfgNode + # Removes all wait times that are not part of the eatery's events class WaitTimeFilter(DfgNode): @@ -21,7 +22,11 @@ def __call__(self, *args, **kwargs): for day_wait_times in eatery.wait_times: filtered_data = [] for wait_time_data in day_wait_times.data: - eatery_events = eatery.events(tzinfo=kwargs.get("tzinfo"), start=kwargs.get("start"), end=kwargs.get("end")) + eatery_events = eatery.events( + tzinfo=kwargs.get("tzinfo"), + start=kwargs.get("start"), + end=kwargs.get("end") + ) if any([wait_time_data.timestamp in event for event in eatery_events]): filtered_data.append(wait_time_data) wait_times_filtered.append(WaitTimesDay( @@ -32,6 +37,6 @@ def __call__(self, *args, **kwargs): eatery_clone.wait_times = wait_times_filtered result.append(eatery_clone) return result - + def description(self): return "WaitTimeFilter" diff --git a/api/dfg/WaitTimes.py b/api/dfg/WaitTimes.py index c16c5ad..93523d3 100644 --- a/api/dfg/WaitTimes.py +++ b/api/dfg/WaitTimes.py @@ -1,16 +1,16 @@ -from typing import Mapping -from django.db.models import Avg from datetime import date, datetime, timedelta + import pytz +from django.db.models import Avg from api.datatype.Eatery import Eatery, EateryID -from api.datatype.WaitTimesDay import WaitTimesDay from api.datatype.WaitTime import WaitTime - +from api.datatype.WaitTimesDay import WaitTimesDay from api.dfg.DfgNode import DfgNode from transactions.models import TransactionHistory from transactions.serializers import TransactionHistorySerializer + class WaitTimes(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: @@ -21,8 +21,9 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: transactions_on_date = {} past_days = [] for i in range(1, 13): - # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the transaction_count - past_day = date - timedelta(days = 7 * i) + # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the + # transaction_count + past_day = date - timedelta(days=7 * i) past_days.append(past_day) transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ .values("eatery_id", "block_end_time") \ @@ -36,7 +37,6 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: transactions_on_date[eatery_id].append(transaction_history) transactions_by_date[date] = transactions_on_date date += timedelta(days=1) - eateries = [] for eatery_id in EateryID: eatery_wait_times_by_day = [] @@ -44,11 +44,15 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: transactions = [] if eatery_id in transactions_by_date[date]: transactions = transactions_by_date[date][eatery_id] - eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day(eatery_id, date, transactions)) + eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day( + eatery_id, + date, + transactions + )) eateries.append( Eatery( - id=eatery_id, - wait_times = eatery_wait_times_by_day + id=eatery_id, + wait_times=eatery_wait_times_by_day ) ) return eateries @@ -56,7 +60,7 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: # Expected amount of time (in seconds) for the length of the line to decrease by 1 person # Returns [lower, expected, upper] @staticmethod - def line_decrease_by_one_time(eatery_id: EateryID) -> float: + def line_decrease_by_one_time(eatery_id: EateryID) -> list[int]: if eatery_id == EateryID.MACS_CAFE: return [24, 27, 30] elif eatery_id == EateryID.MATTINS_CAFE: @@ -67,11 +71,11 @@ def line_decrease_by_one_time(eatery_id: EateryID) -> float: return [4, 8, 12] else: return [18, 21, 24] - - # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the amount of time to check out - # Returns [lower, expected, upper] + + # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the + # amount of time to check out Returns [lower, expected, upper] @staticmethod - def base_time_to_get_food(eatery_id: EateryID) -> float: + def base_time_to_get_food(eatery_id: EateryID) -> list[int]: if eatery_id == EateryID.MACS_CAFE: return [240, 300, 360] elif eatery_id == EateryID.MATTINS_CAFE: @@ -85,17 +89,21 @@ def base_time_to_get_food(eatery_id: EateryID) -> float: @staticmethod def generate_eatery_wait_times_by_day( - eatery_id: EateryID, - date: date, + eatery_id: EateryID, + date: date, transactions: list[TransactionHistorySerializer] ) -> WaitTimesDay: + wait_times_data = [] customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] for index in reversed(range(0, len(transactions))): base_times = WaitTimes.base_time_to_get_food(eatery_id) line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago - how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] + how_long_ago_guest_arrival = ( + base_times[1] + + line_decrease_times[1] * transactions[index].data["transaction_avg"] + ) prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) if prev_bucket_guest_arrival > 9: # TODO: Send a slack error here instead @@ -111,11 +119,13 @@ def generate_eatery_wait_times_by_day( block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() timestamp = int(WaitTimes.timestamp_combined(date, block_end_time) - 5 * 60 / 2) wait_times_data.insert(0, WaitTime( - timestamp=timestamp, - wait_time_low = wait_time_low, - wait_time_expected= wait_time_expected, - wait_time_high = wait_time_high)) - return WaitTimesDay(canonical_date = date, data = wait_times_data) + timestamp=timestamp, + wait_time_low=wait_time_low, + wait_time_expected=wait_time_expected, + wait_time_high=wait_time_high + )) + + return WaitTimesDay(canonical_date=date, data=wait_times_data) @staticmethod def timestamp_combined(date: datetime.date, time: datetime.time): diff --git a/api/dfg/macros/LeftMergeEateries.py b/api/dfg/macros/LeftMergeEateries.py index c25ca34..4fdaef9 100644 --- a/api/dfg/macros/LeftMergeEateries.py +++ b/api/dfg/macros/LeftMergeEateries.py @@ -3,7 +3,9 @@ from api.dfg.EateryFromJson import EateryFromJson from api.dfg.LeftMergeById import LeftMergeById + class LeftMergeEateries(DfgNode): + def __init__(self, left: DfgNode, right: DfgNode): self.macro = EateryFromJson( LeftMergeById( @@ -19,4 +21,4 @@ def __call__(self, *args, **kwargs): return self.macro(*args, **kwargs) def description(self): - return "LeftMergeEateries" \ No newline at end of file + return "LeftMergeEateries" diff --git a/api/views.py b/api/views.py index 3a4f446..e9345ed 100644 --- a/api/views.py +++ b/api/views.py @@ -34,6 +34,7 @@ re_raise_exceptions=True ) + def index(request): tzinfo = pytz.timezone("US/Eastern") reload = request.GET.get('reload') @@ -45,6 +46,7 @@ def index(request): ) return JsonResponse(result) + def google_sheets_eateries(request): # dfg = DictResponseWrapper( # EateryToJson( @@ -60,4 +62,4 @@ def google_sheets_eateries(request): # end=date.today() + timedelta(days=7) # ) - return JsonResponse([]) \ No newline at end of file + return JsonResponse([]) diff --git a/eateries/migrations/0001_initial.py b/eateries/migrations/0001_initial.py new file mode 100644 index 0000000..bd272ef --- /dev/null +++ b/eateries/migrations/0001_initial.py @@ -0,0 +1,105 @@ +# Generated by Django 4.0 on 2022-01-06 14:51 + +import datetime +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='EateryStore', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=40)), + ('menu_summary', models.CharField(max_length=60)), + ('image_url', models.URLField()), + ('location', models.CharField(max_length=30)), + ('campus_area', models.CharField(choices=[('WST', 'West'), ('NRTH', 'North'), ('CNTRL', 'Central'), ('CTWN', 'Collegetown'), ('NN', '')], default='NN', max_length=5)), + ('online_order', models.BooleanField(null=True)), + ('online_order_url', models.URLField()), + ('latitude', models.FloatField(null=True)), + ('longitude', models.FloatField(null=True)), + ('payment_accepts_meal_swipes', models.BooleanField(null=True)), + ('payment_accepts_brbs', models.BooleanField(null=True)), + ('payment_accepts_cash', models.BooleanField(null=True)), + ], + ), + migrations.CreateModel( + name='MenuStore', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=40)), + ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.CreateModel( + name='RepeatingEventSchedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('day_of_week', models.CharField(choices=[('MON', 'Monday'), ('TUE', 'Tuesday'), ('WED', 'Wednesday'), ('THU', 'Thursday'), ('FRI', 'Friday'), ('SAT', 'Saturday'), ('SUN', 'Sunday')], max_length=3)), + ('event_description', models.CharField(choices=[('BRKFST', 'Breakfast'), ('LNCH', 'Lunch'), ('DNNR', 'Dinner'), ('GNRL', 'General')], max_length=10)), + ('start', models.TimeField()), + ('end', models.TimeField()), + ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ], + ), + migrations.CreateModel( + name='MenuItemStore', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=40)), + ('description', models.CharField(max_length=50)), + ('price', models.FloatField(null=True)), + ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.CreateModel( + name='ForSale', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category', models.CharField(max_length=40)), + ('item_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menuitemstore')), + ('menu_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ], + ), + migrations.CreateModel( + name='ExceptionStore', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.CharField(max_length=80)), + ('start_timestamp', models.DateTimeField()), + ('end_timestamp', models.DateTimeField()), + ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.CreateModel( + name='EventChangeLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(default=datetime.datetime.now)), + ('type', models.CharField(choices=[('INS', 'Insert'), ('DEL', 'Delete')], max_length=3)), + ('event_description', models.CharField(choices=[('BRKFST', 'Breakfast'), ('LNCH', 'Lunch'), ('DNNR', 'Dinner'), ('GNRL', 'General')], max_length=10)), + ('canonical_date', models.DateField()), + ('start', models.DateTimeField()), + ('end', models.DateTimeField()), + ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ], + ), + migrations.AddIndex( + model_name='eventchangelog', + index=models.Index(fields=['eatery_id', 'canonical_date'], name='eateries_ev_eatery__d34bcb_idx'), + ), + migrations.AddIndex( + model_name='eventchangelog', + index=models.Index(fields=['created_at'], name='eateries_ev_created_d237e2_idx'), + ), + ] diff --git a/eateries/models.py b/eateries/models.py index 49be2af..315ccb1 100644 --- a/eateries/models.py +++ b/eateries/models.py @@ -1,13 +1,88 @@ from django.db import models +from datetime import datetime +class EateryStore(models.Model): + class CampusArea(models.TextChoices): + WEST = 'WST', 'West' + NORTH = 'NRTH', 'North' + CENTRAL = 'CNTRL', 'Central' + COLLEGETOWN = 'CTWN', 'Collegetown' + NONE = 'NN', '' + + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=40) + menu_summary = models.CharField(max_length = 60) + image_url = models.URLField() + location = models.CharField(max_length=30) + campus_area = models.CharField(max_length=5, choices=CampusArea.choices, default=CampusArea.NONE) + online_order = models.BooleanField(null = True) + online_order_url = models.URLField() + latitude = models.FloatField(null = True) + longitude = models.FloatField(null = True) + payment_accepts_meal_swipes = models.BooleanField(null = True) + payment_accepts_brbs = models.BooleanField(null = True) + payment_accepts_cash = models.BooleanField(null = True) + +class ExceptionStore(models.Model): + eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + description = models.CharField(max_length = 80) + start_timestamp = models.DateTimeField() + end_timestamp = models.DateTimeField() + +class MenuItemStore(models.Model): + eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + name = models.CharField(max_length=40) + description = models.CharField(max_length = 50) + price = models.FloatField(null = True) + +class MenuStore(models.Model): + eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + name = models.CharField(max_length = 40) + +class ForSale(models.Model): + item_id = models.ForeignKey(MenuItemStore, on_delete=models.DO_NOTHING) + menu_id = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + category = models.CharField(max_length=40) + +class EventDescription(models.TextChoices): + BREAKFAST = 'BRKFST' + LUNCH = 'LNCH' + DINNER = 'DNNR' + GENERAL = 'GNRL' +class RepeatingEventSchedule(models.Model): + class DayOfTheWeek(models.TextChoices): + MONDAY = 'MON' + TUESDAY = 'TUE' + WEDNESDAY = 'WED' + THURSDAY = 'THU' + FRIDAY = 'FRI' + SATURDAY = 'SAT' + SUNDAY = 'SUN' + + eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=3) + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + start = models.TimeField() + end = models.TimeField() + +class EventChangeLog(models.Model): + class Meta: + indexes = [ + models.Index(fields = ['eatery_id', 'canonical_date']), + models.Index(fields = ['created_at']) + ] + class ChangeLogType(models.TextChoices): + INSERT = 'INS' + DELETE = 'DEL' + + eatery_id = models.ForeignKey(EateryStore, on_delete = models.DO_NOTHING) + menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + created_at = models.DateTimeField(default=datetime.now) + type = models.CharField(choices=ChangeLogType.choices, max_length=3) + event_description = models.CharField(choices=EventDescription.choices, max_length= 10) + canonical_date = models.DateField() + start = models.DateTimeField() + end = models.DateTimeField() -# Create your models here. -# class Eatery(models.Model): -# class Meta: -# unique_together = ('name', 'block_end_time', 'canonical_date') -# indexes = [models.Index(fields = ['canonical_date'])] -# canonical_date = models.DateField() -# block_end_time = models.TimeField() -# transaction_count = models.IntegerField() -# TODO: Models for hard-coded eateries \ No newline at end of file From 416f1a7891dc880a0fb9636be9af5f0cbfbe1b7d Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 6 Jan 2022 12:54:10 -0500 Subject: [PATCH 031/305] Update location of scripts --- eatery_blue_backend/urls.py | 1 - .../controllers/mock_vendor_controller.py | 28 ------------- .../update_transactions_controller.py | 6 --- .../commands/fetch_recent_transactions.py | 26 ++++++++++++ .../commands/ingest_log_transactions.py | 32 +++++++++++++++ .../scripts/ingest_log_transactions.py | 40 ------------------- transactions/urls.py | 10 ----- transactions/views.py | 28 +------------ 8 files changed, 59 insertions(+), 112 deletions(-) delete mode 100644 transactions/controllers/mock_vendor_controller.py create mode 100644 transactions/management/commands/fetch_recent_transactions.py create mode 100644 transactions/management/commands/ingest_log_transactions.py delete mode 100644 transactions/scripts/ingest_log_transactions.py delete mode 100644 transactions/urls.py diff --git a/eatery_blue_backend/urls.py b/eatery_blue_backend/urls.py index ea8f685..8db22b1 100644 --- a/eatery_blue_backend/urls.py +++ b/eatery_blue_backend/urls.py @@ -18,6 +18,5 @@ urlpatterns = [ path("api", include("api.urls")), - path("", include("transactions.urls")), path("admin/", admin.site.urls), ] diff --git a/transactions/controllers/mock_vendor_controller.py b/transactions/controllers/mock_vendor_controller.py deleted file mode 100644 index 37a0479..0000000 --- a/transactions/controllers/mock_vendor_controller.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.contrib.auth.models import User -from django.core.paginator import Paginator -from django.db.models import Q -from django.http import JsonResponse -from datetime import datetime -from pytz import timezone -from random import randrange - -class MockVendorController: - def process(self): - tz = timezone("EST") - now = datetime.now(tz) - recent_timestamp = datetime(now.year, now.month, now.day, now.hour, now.minute - now.minute%5, 0, 0) - places = ['Bear Necessities', 'North Star Marketplace', "Jansen's Market", 'StockingHallCafe', 'Marthas', 'Cafe Jennie', "Goldie's Cafe", 'Alice Cook House', 'Carl Becker House', 'Duffield', 'Green Dragon', 'Trillium', 'Olin Libe Cafe', 'Carols Cafe', 'Statler Terrace', 'Bus Stop Bagels', 'Stocking Hall', 'Kosher', 'Jansens at Bethe House', 'Keeton House', 'RPME', 'Rose House', 'Catering', 'DIN Special Event', 'Risley', "Franny's FT", 'HA3350', "McCormick's", 'Statler Banfi', "Statler Banfi's", 'Statler Regent', 'Sage', 'Straight Market', 'Concession Suite', 'Crossings Cafe', 'Okenshields', 'Big Red Barn', 'Rustys', 'Mann Cafe', 'Statler Macs', 'Morrison'] - units = [] - for place in places: - crowd_count = randrange(6) - if crowd_count > 0: - units.append({ - "UNIT_NAME": place, - "CROWD_COUNT": crowd_count - }) - - return { - "TIMESTAMP": recent_timestamp.strftime('%Y-%m-%d %I:%M:%S %p'), - "LOOK_BACK_MINUTES": 5, - "UNITS": units - } diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index f5fcbd0..9b5924c 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -86,12 +86,6 @@ def __init__(self, data): self._data = data def process(self): - if "error" in self._data: - return { - "success": False, - "result": None, - "error": self._data["error"] - } if self._data["TIMESTAMP"] == "Invalid date": return { "success": False, diff --git a/transactions/management/commands/fetch_recent_transactions.py b/transactions/management/commands/fetch_recent_transactions.py new file mode 100644 index 0000000..f65018f --- /dev/null +++ b/transactions/management/commands/fetch_recent_transactions.py @@ -0,0 +1,26 @@ +# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db + +import requests +import os +from requests.structures import CaseInsensitiveDict +from django.core.management.base import BaseCommand +from transactions.controllers.update_transactions_controller import UpdateTransactionsController + +class Command(BaseCommand): + help = 'Fetches transaction data from a vendor API and adds it to our transaction history database' + + def handle(self, *args, **options): + endpoint = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" + headers = CaseInsensitiveDict() + token = os.environ.get("CORNELL_VENDOR_TOKEN") + api_key = os.environ.get("CORNELL_VENDOR_API_KEY") + headers["Accept"] = "application/json" + headers["Authorization"] = "Bearer {}".format(token) + headers["X-Api-Key"] = api_key + resp = requests.get(endpoint, headers=headers) + num_inserted = 0 + if resp.status_code == 200: + res = UpdateTransactionsController(resp.json()).process() + if res["success"]: + num_inserted = res["result"]["num_inserted"] + print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file diff --git a/transactions/management/commands/ingest_log_transactions.py b/transactions/management/commands/ingest_log_transactions.py new file mode 100644 index 0000000..6d14b93 --- /dev/null +++ b/transactions/management/commands/ingest_log_transactions.py @@ -0,0 +1,32 @@ +# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db + +import json +from datetime import datetime +from django.core.management.base import BaseCommand + +from transactions.controllers.update_transactions_controller import UpdateTransactionsController +from transactions.models import TransactionHistory + +class Command(BaseCommand): + help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistory table' + + def handle(self, *args, **options): + num_deleted = TransactionHistory.objects.all().delete()[0] + counter = 0 + num_inserted = 0 + with open("static_sources/data.log", "r") as log: + for line in log: + try: + data = json.loads(line) + timestamp = datetime.strptime(data['TIMESTAMP'], '%Y-%m-%d %I:%M:%S %p') + if counter % 100 == 1: + print(timestamp) + if timestamp.year == 2021 and timestamp.month > 7: + counter += 1 + res = UpdateTransactionsController(data).process() + if res["success"]: + num_inserted += res["result"]["num_inserted"] + except Exception as e: + pass + print("{} Entries Deleted".format(num_deleted)) + print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file diff --git a/transactions/scripts/ingest_log_transactions.py b/transactions/scripts/ingest_log_transactions.py deleted file mode 100644 index 1eac8a2..0000000 --- a/transactions/scripts/ingest_log_transactions.py +++ /dev/null @@ -1,40 +0,0 @@ -# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db - -import json -import time -from django.http import JsonResponse -from rest_framework.decorators import api_view -from django.views.decorators.csrf import csrf_exempt - -from transactions.controllers.update_transactions_controller import UpdateTransactionsController -from ..models import TransactionHistory - -@api_view(['POST']) -@csrf_exempt -def ingest(request): - num_deleted = TransactionHistory.objects.all().delete()[0] - counter = 0 - num_inserted = 0 - with open("static_sources/data.log", "r") as log: - for line in log: - try: - data = json.loads(line) - timestamp = time.strptime(data['TIMESTAMP'], '%Y-%m-%d %I:%M:%S %p') - if counter % 100 == 1: - print(timestamp) - if timestamp.tm_year == 2021 and timestamp.tm_mon > 7: - counter += 1 - res = UpdateTransactionsController(data).process() - if res["success"]: - num_inserted += res["result"]["num_inserted"] - except: - # Reading a blank line or INVALID DATE - pass - return JsonResponse({ - "success": True, - "result": { - "num_inserted": num_inserted, - "num_deleted": num_deleted - }, - "error": None - }) \ No newline at end of file diff --git a/transactions/urls.py b/transactions/urls.py deleted file mode 100644 index f79f2eb..0000000 --- a/transactions/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path - -from . import views -from transactions.scripts import ingest_log_transactions - -urlpatterns = [ - path("fetch_recent_transactions", views.autofetch_recent_transactions, name="autofetch_recent_transactions"), - path("mock_vendor_endpoint", views.mock_vendor_endpoint, name="mock_vendor_endpoint"), - path("ingest_log_transactions", ingest_log_transactions.ingest, name="ingest_log_transactions") -] diff --git a/transactions/views.py b/transactions/views.py index e4f309f..b91e46a 100644 --- a/transactions/views.py +++ b/transactions/views.py @@ -1,29 +1,3 @@ from django.shortcuts import render -from django.http import JsonResponse -from requests.structures import CaseInsensitiveDict -from .controllers.mock_vendor_controller import MockVendorController -from .controllers.update_transactions_controller import UpdateTransactionsController -import requests -import os - -def mock_vendor_endpoint(request): - return JsonResponse(MockVendorController().process()) - -# Called every 5 minutes, updates TransactionHistory database -def autofetch_recent_transactions(request): - # endpoint = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" - endpoint = "http://localhost:8000/mock_vendor_endpoint" - headers = CaseInsensitiveDict() - token = os.environ.get("CORNELL_VENDOR_TOKEN") - api_key = os.environ.get("CORNELL_VENDOR_API_KEY") - headers["Accept"] = "application/json" - headers["Authorization"] = "Bearer {}".format(token) - headers["X-Api-Key"] = api_key - - resp = requests.get(endpoint, headers=headers) - if resp.status_code == 200: - return JsonResponse(UpdateTransactionsController(resp.json()).process()) - else: - return JsonResponse(UpdateTransactionsController({"error": resp.json()}).process()) - \ No newline at end of file +# Create your views here. \ No newline at end of file From f95b1dcd886488a697a9ed1f572a16d3124c2809 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Fri, 7 Jan 2022 17:06:33 -0500 Subject: [PATCH 032/305] Events refactor --- api/datatype/Event.py | 7 +- api/dfg/CornellDiningNow.py | 154 +----------------- api/dfg/macros/EateryEvents.py | 34 ++++ api/dfg/macros/LeftMergeEateries.py | 21 ++- api/dfg/macros/LeftMergeEvents.py | 35 ++++ api/dfg/schedule/ClosedSchedule.py | 19 +++ api/dfg/schedule/CornellDiningEvents.py | 113 +++++++++++++ api/dfg/schedule/DateSchedule.py | 15 ++ api/dfg/schedule/DayOfWeekSchedule.py | 16 ++ .../ConvertFromJson.py} | 27 ++- .../ConvertToJson.py} | 12 +- api/dfg/{ => util}/DictResponseWrapper.py | 0 api/dfg/util/EateryGenerator.py | 25 +++ api/dfg/{ => util}/InMemoryCache.py | 6 +- .../{LeftMergeById.py => util/LeftMerge.py} | 19 ++- api/dfg/util/Mapping.py | 20 +++ api/dfg/{ => wait_times}/WaitTimeFilter.py | 6 +- api/dfg/{ => wait_times}/WaitTimes.py | 74 ++++----- api/util.py | 71 ++++++++ api/views.py | 45 +++-- eateries/admin.py | 15 +- eateries/migrations/0001_initial.py | 107 +++++++----- ..._alter_eaterystore_campus_area_and_more.py | 73 --------- ...price_menuitemstore_base_price_and_more.py | 30 ---- .../0004_alter_menuitemstore_description.py | 18 -- ...alter_menuitemstore_base_price_and_more.py | 28 ---- ...ter_exceptionstore_description_and_more.py | 23 --- eateries/models.py | 68 ++++---- .../commands/ingest_log_transactions.py | 32 ++++ 29 files changed, 617 insertions(+), 496 deletions(-) create mode 100644 api/dfg/macros/EateryEvents.py create mode 100644 api/dfg/macros/LeftMergeEvents.py create mode 100644 api/dfg/schedule/ClosedSchedule.py create mode 100644 api/dfg/schedule/CornellDiningEvents.py create mode 100644 api/dfg/schedule/DateSchedule.py create mode 100644 api/dfg/schedule/DayOfWeekSchedule.py rename api/dfg/{EateryFromJson.py => util/ConvertFromJson.py} (52%) rename api/dfg/{EateryToJson.py => util/ConvertToJson.py} (69%) rename api/dfg/{ => util}/DictResponseWrapper.py (100%) create mode 100644 api/dfg/util/EateryGenerator.py rename api/dfg/{ => util}/InMemoryCache.py (91%) rename api/dfg/{LeftMergeById.py => util/LeftMerge.py} (71%) create mode 100644 api/dfg/util/Mapping.py rename api/dfg/{ => wait_times}/WaitTimeFilter.py (84%) rename api/dfg/{ => wait_times}/WaitTimes.py (64%) create mode 100644 api/util.py delete mode 100644 eateries/migrations/0002_alter_eaterystore_campus_area_and_more.py delete mode 100644 eateries/migrations/0003_rename_price_menuitemstore_base_price_and_more.py delete mode 100644 eateries/migrations/0004_alter_menuitemstore_description.py delete mode 100644 eateries/migrations/0005_alter_menuitemstore_base_price_and_more.py delete mode 100644 eateries/migrations/0006_alter_exceptionstore_description_and_more.py create mode 100644 transactions/management/commands/ingest_log_transactions.py diff --git a/api/datatype/Event.py b/api/datatype/Event.py index 8a19bd8..97e1975 100644 --- a/api/datatype/Event.py +++ b/api/datatype/Event.py @@ -23,7 +23,12 @@ def __init__( self.end_timestamp = end_timestamp self.menu = menu - def to_json(self): + def to_json( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None + ): return { "description": self.description, "canonical_date": str(self.canonical_date), diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index 6a5bdf4..13293a4 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -2,21 +2,16 @@ from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem +from api.datatype.Eatery import Eatery +from api.util import dining_id_to_internal_id, CORNELL_DINING_URL from datetime import date - class CornellDiningNow(DfgNode): - CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" def __call__(self, *args, **kwargs) -> list[Eatery]: try: - response = requests.get(CornellDiningNow.CORNELL_DINING_URL).json() + response = requests.get(CORNELL_DINING_URL).json() except Exception as e: raise e @@ -33,21 +28,13 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: @staticmethod def parse_eatery(json_eatery: dict) -> Eatery: - is_cafe = "Cafe" in { - eatery_type["descr"] - for eatery_type in json_eatery["eateryTypes"] - } + # Events are parsed later return Eatery( - id=CornellDiningNow.dining_id_to_internal_id(json_eatery["id"]), + id=dining_id_to_internal_id(json_eatery["id"]), name=json_eatery["name"], campus_area=json_eatery["campusArea"]["descrshort"], latitude=json_eatery["latitude"], longitude=json_eatery["longitude"], - events=CornellDiningNow.eatery_events_from_json( - json_operating_hours=json_eatery["operatingHours"], - json_dining_items=json_eatery["diningItems"], - is_cafe=is_cafe - ), payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), location=json_eatery["location"], online_order=json_eatery["onlineOrdering"], @@ -68,136 +55,5 @@ def generate_payment_methods(json_paymethods: list): payment_methods.append("swipes") return payment_methods - @staticmethod - def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: - json_operating_hours = sorted( - json_operating_hours, - key=lambda json_date_events: json_date_events["date"] - ) - events = [] - - for json_date_events in json_operating_hours: - canonical_date = date.fromisoformat(json_date_events["date"]) - - for json_event in json_date_events["events"]: - events.append(Event( - canonical_date=canonical_date, - description=json_event["descr"], - start_timestamp=json_event["startTimestamp"], - end_timestamp=json_event["endTimestamp"], - menu=CornellDiningNow.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe) - )) - - return events - - @staticmethod - def eatery_menu_from_json(json_menu: list, json_dining_items: list, is_cafe: bool): - if is_cafe: - return CornellDiningNow.cafe_menu_from_json(json_dining_items) - else: - return CornellDiningNow.dining_hall_menu_from_json(json_menu) - - @staticmethod - def cafe_menu_from_json(json_dining_items: list) -> Menu: - category_map = {} - for item in json_dining_items: - if item['category'] not in category_map: - category_map[item['category']] = [] - category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) - categories = [] - for category_name in category_map: - categories.append(MenuCategory(category_name, category_map[category_name])) - return Menu(categories=categories) - - @staticmethod - def dining_hall_menu_from_json(json_menu: list) -> Menu: - json_menu = sorted( - json_menu, - key=lambda json_menu_category: json_menu_category["sortIdx"] - ) - menu_categories = [] - - for json_menu_category in json_menu: - items = [ - MenuItem.from_cornell_dining_json(json_item) - for json_item in json_menu_category["items"] - ] - - menu_categories.append(MenuCategory( - category=json_menu_category["category"], - items=items - )) - - return Menu(categories=menu_categories) - - @staticmethod - def dining_id_to_internal_id(id: int): - if id == 31: - return EateryID.ONE_ZERO_FOUR_WEST - elif id == 7: - return EateryID.LIBE_CAFE - elif id == 8: - return EateryID.ATRIUM_CAFE - elif id == 1: - return EateryID.BEAR_NECESSITIES - elif id == 25: - return EateryID.BECKER_HOUSE - elif id == 10: - return EateryID.BIG_RED_BARN - elif id == 11: - return EateryID.BUS_STOP_BAGELS - elif id == 12: - return EateryID.CAFE_JENNIE - elif id == 2: - return EateryID.CAROLS_CAFE - elif id == 26: - return EateryID.COOK_HOUSE - elif id == 14: - return EateryID.DAIRY_BAR - elif id == 41: - return EateryID.CROSSINGS_CAFE - elif id == 32: - return EateryID.FRANNYS - elif id == 16: - return EateryID.GOLDIES_CAFE - elif id == 15: - return EateryID.GREEN_DRAGON - elif id == 24: - return EateryID.HOT_DOG_CART - elif id == 34: - return EateryID.ICE_CREAM_BIKE - elif id == 27: - return EateryID.BETHE_HOUSE - elif id == 28: - return EateryID.JANSENS_MARKET - elif id == 29: - return EateryID.KEETON_HOUSE - elif id == 42: - return EateryID.MANN_CAFE - elif id == 18: - return EateryID.MARTHAS_CAFE - elif id == 19: - return EateryID.MATTINS_CAFE - elif id == 33: - return EateryID.MCCORMICKS - elif id == 3: - return EateryID.NORTH_STAR_DINING - elif id == 20: - return EateryID.OKENSHIELDS - elif id == 4: - return EateryID.RISLEY - elif id == 5: - return EateryID.RPCC - elif id == 30: - return EateryID.ROSE_HOUSE - elif id == 21: - return EateryID.RUSTYS - elif id == 13: - return EateryID.STRAIGHT_FROM_THE_MARKET - elif id == 23: - return EateryID.TRILLIUM - else: - return None - def description(self): return "CornellDiningNow" diff --git a/api/dfg/macros/EateryEvents.py b/api/dfg/macros/EateryEvents.py new file mode 100644 index 0000000..204e5e7 --- /dev/null +++ b/api/dfg/macros/EateryEvents.py @@ -0,0 +1,34 @@ +from api.dfg.DfgNode import DfgNode + +from api.dfg.schedule.ClosedSchedule import ClosedSchedule +from api.dfg.schedule.DayOfWeekSchedule import DayOfWeekSchedule +from api.dfg.schedule.DateSchedule import DateSchedule +from api.dfg.schedule.CornellDiningEvents import CornellDiningEvents + +from api.dfg.macros.LeftMergeEvents import LeftMergeEvents + +from api.datatype.Eatery import EateryID + +# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if +# conflict) +class EateryEvents(DfgNode): + def __init__(self, eatery_id: EateryID, cache): + self.macro = ClosedSchedule( + eatery_id, + LeftMergeEvents( + DateSchedule(eatery_id), + LeftMergeEvents( + DayOfWeekSchedule(eatery_id), + CornellDiningEvents(eatery_id, cache) + ) + ) + ) + + def children(self): + return self.macro.children() + + def __call__(self, *args, **kwargs): + return self.macro(*args, **kwargs) + + def description(self): + return "LeftMergeEvents" \ No newline at end of file diff --git a/api/dfg/macros/LeftMergeEateries.py b/api/dfg/macros/LeftMergeEateries.py index 4fdaef9..00ec52f 100644 --- a/api/dfg/macros/LeftMergeEateries.py +++ b/api/dfg/macros/LeftMergeEateries.py @@ -1,16 +1,23 @@ from api.dfg.DfgNode import DfgNode -from api.dfg.EateryToJson import EateryToJson -from api.dfg.EateryFromJson import EateryFromJson -from api.dfg.LeftMergeById import LeftMergeById - +from api.dfg.util.ConvertToJson import ConvertToJson +from api.dfg.util.ConvertFromJson import EateryFromJson +from api.dfg.util.LeftMerge import LeftMerge class LeftMergeEateries(DfgNode): def __init__(self, left: DfgNode, right: DfgNode): + def comparator(left, right): + if left["id"] == right["id"]: + return 0 + elif left["id"] < right["id"]: + return -1 + else: + return 1 self.macro = EateryFromJson( - LeftMergeById( - EateryToJson(left), - EateryToJson(right) + LeftMerge( + ConvertToJson(left), + ConvertToJson(right), + comparator ) ) diff --git a/api/dfg/macros/LeftMergeEvents.py b/api/dfg/macros/LeftMergeEvents.py new file mode 100644 index 0000000..f3adda3 --- /dev/null +++ b/api/dfg/macros/LeftMergeEvents.py @@ -0,0 +1,35 @@ +from api.dfg.DfgNode import DfgNode + +from api.dfg.util.ConvertToJson import ConvertToJson +from api.dfg.util.ConvertFromJson import EventFromJson +from api.dfg.util.LeftMerge import LeftMerge + +# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if +# conflict) +class LeftMergeEvents(DfgNode): + def __init__(self, left: DfgNode, right: DfgNode): + def comparator(left, right): + left_val = (left["canonical_date"], left["description"]) + right_val = (right["canonical_date"], right["description"]) + if left_val == right_val: + return 0 + elif left_val < right_val: + return -1 + else: + return 1 + self.macro = EventFromJson( + LeftMerge( + ConvertToJson(left), + ConvertToJson(right), + comparator + ) + ) + + def children(self): + return self.macro.children() + + def __call__(self, *args, **kwargs): + return self.macro(*args, **kwargs) + + def description(self): + return "LeftMergeEvents" \ No newline at end of file diff --git a/api/dfg/schedule/ClosedSchedule.py b/api/dfg/schedule/ClosedSchedule.py new file mode 100644 index 0000000..f53de12 --- /dev/null +++ b/api/dfg/schedule/ClosedSchedule.py @@ -0,0 +1,19 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from eateries.models import ClosedEventSchedule + +class ClosedSchedule(DfgNode): + + def __init__(self, eatery_id: EateryID, child: DfgNode): + self.eatery_id = eatery_id + self.child = child + + def __call__(self, *args, **kwargs) -> list[Eatery]: + ClosedEventSchedule.objects.all() + return self.child(*args, **kwargs) + + def children(self): + return [self.child] + + def description(self): + return "ClosedSchedule" diff --git a/api/dfg/schedule/CornellDiningEvents.py b/api/dfg/schedule/CornellDiningEvents.py new file mode 100644 index 0000000..f68fcf3 --- /dev/null +++ b/api/dfg/schedule/CornellDiningEvents.py @@ -0,0 +1,113 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem +from api.util import dining_id_to_internal_id, CORNELL_DINING_URL + +from datetime import date +import requests + +class CornellDiningEvents(DfgNode): + + def __init__(self, eatery_id: EateryID, cache): + self.eatery_id = eatery_id + self.cache = cache + + def __call__(self, *args, **kwargs) -> list[Eatery]: + if "eateries" not in self.cache: + try: + response = requests.get(CORNELL_DINING_URL).json() + except Exception as e: + raise e + if response["status"] == "success": + json_eateries = response["data"]["eateries"] + eateries = [] + for json_eatery in json_eateries: + eateries.append(CornellDiningEvents.parse_eatery(json_eatery)) + self.cache["eateries"] = eateries + for eatery in self.cache["eateries"]: + if eatery.id == self.eatery_id: + return eatery.events() + return [] + + @staticmethod + def parse_eatery(json_eatery: dict) -> Eatery: + is_cafe = "Cafe" in { + eatery_type["descr"] + for eatery_type in json_eatery["eateryTypes"] + } + return Eatery( + id=dining_id_to_internal_id(json_eatery["id"]), + events=CornellDiningEvents.eatery_events_from_json( + json_operating_hours=json_eatery["operatingHours"], + json_dining_items=json_eatery["diningItems"], + is_cafe=is_cafe + ) + ) + @staticmethod + def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: + json_operating_hours = sorted( + json_operating_hours, + key=lambda json_date_events: json_date_events["date"] + ) + events = [] + + for json_date_events in json_operating_hours: + canonical_date = date.fromisoformat(json_date_events["date"]) + + for json_event in json_date_events["events"]: + events.append(Event( + canonical_date=canonical_date, + description=json_event["descr"], + start_timestamp=json_event["startTimestamp"], + end_timestamp=json_event["endTimestamp"], + menu=CornellDiningEvents.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe) + )) + + return events + + @staticmethod + def eatery_menu_from_json(json_menu: list, json_dining_items: list, is_cafe: bool): + if is_cafe: + return CornellDiningEvents.cafe_menu_from_json(json_dining_items) + else: + return CornellDiningEvents.dining_hall_menu_from_json(json_menu) + + @staticmethod + def cafe_menu_from_json(json_dining_items: list) -> Menu: + category_map = {} + for item in json_dining_items: + if item['category'] not in category_map: + category_map[item['category']] = [] + category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) + categories = [] + for category_name in category_map: + categories.append(MenuCategory(category_name, category_map[category_name])) + return Menu(categories=categories) + + @staticmethod + def dining_hall_menu_from_json(json_menu: list) -> Menu: + json_menu = sorted( + json_menu, + key=lambda json_menu_category: json_menu_category["sortIdx"] + ) + menu_categories = [] + + for json_menu_category in json_menu: + items = [ + MenuItem.from_cornell_dining_json(json_item) + for json_item in json_menu_category["items"] + ] + + menu_categories.append(MenuCategory( + category=json_menu_category["category"], + items=items + )) + + return Menu(categories=menu_categories) + + + def description(self): + return "CornellDiningEvents" \ No newline at end of file diff --git a/api/dfg/schedule/DateSchedule.py b/api/dfg/schedule/DateSchedule.py new file mode 100644 index 0000000..5033a0d --- /dev/null +++ b/api/dfg/schedule/DateSchedule.py @@ -0,0 +1,15 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from eateries.models import DateEventSchedule + +class DateSchedule(DfgNode): + + def __init__(self, eatery_id: EateryID): + self.eatery_id = eatery_id + + def __call__(self, *args, **kwargs) -> list[Eatery]: + DateEventSchedule.objects.all() + return [] + + def description(self): + return "EateryStubs" diff --git a/api/dfg/schedule/DayOfWeekSchedule.py b/api/dfg/schedule/DayOfWeekSchedule.py new file mode 100644 index 0000000..7510b87 --- /dev/null +++ b/api/dfg/schedule/DayOfWeekSchedule.py @@ -0,0 +1,16 @@ + +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from eateries.models import DayOfWeekEventSchedule + +class DayOfWeekSchedule(DfgNode): + + def __init__(self, eatery_id: EateryID): + self.eatery_id = eatery_id + + def __call__(self, *args, **kwargs) -> list[Eatery]: + DayOfWeekEventSchedule.objects.all() + return [] + + def description(self): + return "DayOfWeekSchedule" diff --git a/api/dfg/EateryFromJson.py b/api/dfg/util/ConvertFromJson.py similarity index 52% rename from api/dfg/EateryFromJson.py rename to api/dfg/util/ConvertFromJson.py index 8466771..b85f28f 100644 --- a/api/dfg/EateryFromJson.py +++ b/api/dfg/util/ConvertFromJson.py @@ -2,7 +2,7 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery - +from api.datatype.Event import Event class EateryFromJson(DfgNode): @@ -28,3 +28,28 @@ def from_json(obj: Union[list, dict], *args, **kwargs): def description(self): return "EateryFromJson" + +class EventFromJson(DfgNode): + + def __init__(self, child: DfgNode): + self.child = child + + def __call__(self, *args, **kwargs): + result = self.child(*args, **kwargs) + return EventFromJson.from_json(result, *args, **kwargs) + + def children(self): + return [self.child] + + @staticmethod + def from_json(obj: Union[list, dict], *args, **kwargs): + if isinstance(obj, list): + return [ + EventFromJson.from_json(elem, *args, **kwargs) + for elem in obj + ] + else: + return Event.from_json(obj) + + def description(self): + return "EventFromJson" diff --git a/api/dfg/EateryToJson.py b/api/dfg/util/ConvertToJson.py similarity index 69% rename from api/dfg/EateryToJson.py rename to api/dfg/util/ConvertToJson.py index 51889ba..25129f0 100644 --- a/api/dfg/EateryToJson.py +++ b/api/dfg/util/ConvertToJson.py @@ -1,26 +1,26 @@ from typing import Union +from api.datatype.Event import Event from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery - -class EateryToJson(DfgNode): +class ConvertToJson(DfgNode): def __init__(self, child: DfgNode): self.child = child def __call__(self, *args, **kwargs): result = self.child(*args, **kwargs) - return EateryToJson.to_json(result, *args, **kwargs) + return ConvertToJson.to_json(result, *args, **kwargs) def children(self): return [self.child] @staticmethod - def to_json(obj: Union[list, Eatery], *args, **kwargs): + def to_json(obj: Union[list, Eatery, Event], *args, **kwargs): if isinstance(obj, list): return [ - EateryToJson.to_json(elem, *args, **kwargs) + ConvertToJson.to_json(elem, *args, **kwargs) for elem in obj ] else: @@ -31,4 +31,4 @@ def to_json(obj: Union[list, Eatery], *args, **kwargs): ) def description(self): - return "EateryToJson" + return "ConvertToJson" diff --git a/api/dfg/DictResponseWrapper.py b/api/dfg/util/DictResponseWrapper.py similarity index 100% rename from api/dfg/DictResponseWrapper.py rename to api/dfg/util/DictResponseWrapper.py diff --git a/api/dfg/util/EateryGenerator.py b/api/dfg/util/EateryGenerator.py new file mode 100644 index 0000000..7cbb6dc --- /dev/null +++ b/api/dfg/util/EateryGenerator.py @@ -0,0 +1,25 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from typing import Optional + +class EateryGenerator(DfgNode): + + def __init__( + self, + eatery_id: EateryID, + events_dfg: Optional[DfgNode] = None, + wait_times_dfg: Optional[DfgNode] = None + ): + self.eatery_id = eatery_id + self.events_dfg = events_dfg + self.wait_times_dfg = wait_times_dfg + + def __call__(self, *args, **kwargs) -> list: + return Eatery( + id = self.eatery_id, + events=None if self.events_dfg is None else self.events_dfg(*args, **kwargs), + wait_times=None if self.wait_times_dfg is None else self.wait_times_dfg(*args, **kwargs) + ) + + def description(self): + return "EateryGenerator" diff --git a/api/dfg/InMemoryCache.py b/api/dfg/util/InMemoryCache.py similarity index 91% rename from api/dfg/InMemoryCache.py rename to api/dfg/util/InMemoryCache.py index 3c1bd0e..35b5b5f 100644 --- a/api/dfg/InMemoryCache.py +++ b/api/dfg/util/InMemoryCache.py @@ -3,7 +3,7 @@ from api.dfg.DfgNode import DfgNode from typing import Optional -from api.dfg.EateryToJson import EateryToJson +from api.dfg.util.ConvertToJson import ConvertToJson class DataSnapshot: @@ -21,7 +21,7 @@ def get_data(self): return self.data def to_json(self): - return EateryToJson.to_json(self.data, *self.args, **self.kwargs) + return ConvertToJson.to_json(self.data, *self.args, **self.kwargs) def get_recorded_time(self): return self.recorded_time @@ -69,7 +69,7 @@ def to_json(self, *args, **kwargs): for snapshot in self.snapshots: if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): return snapshot.to_json() - return EateryToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) + return ConvertToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) def description(self): return "InMemoryCache" diff --git a/api/dfg/LeftMergeById.py b/api/dfg/util/LeftMerge.py similarity index 71% rename from api/dfg/LeftMergeById.py rename to api/dfg/util/LeftMerge.py index 84cd8c3..557daf1 100644 --- a/api/dfg/LeftMergeById.py +++ b/api/dfg/util/LeftMerge.py @@ -1,25 +1,28 @@ from api.dfg.DfgNode import DfgNode - +from typing import Callable, TypeVar +from functools import cmp_to_key +T = TypeVar("T") # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if # conflict) -class LeftMergeById(DfgNode): +class LeftMerge(DfgNode): - def __init__(self, left: DfgNode, right: DfgNode): + def __init__(self, left: DfgNode, right: DfgNode, comparator: Callable[[T, T], int]): self.left = left self.right = right + self.comparator = comparator def children(self): return [self.left, self.right] def __call__(self, *args, **kwargs): - left_lst = sorted(self.left(*args, **kwargs), key=lambda x: x["id"]) - right_lst = sorted(self.right(*args, **kwargs), key=lambda x: x["id"]) + left_lst = sorted(self.left(*args, **kwargs), key=cmp_to_key(self.comparator)) + right_lst = sorted(self.right(*args, **kwargs), key=cmp_to_key(self.comparator)) left_json = _pop_first(left_lst) right_json = _pop_first(right_lst) merged_lst = [] while left_json is not None and right_json is not None: - if left_json["id"] == right_json["id"]: + if self.comparator(left_json, right_json) == 0: merged_json = {} for key in right_json: if right_json[key] is not None: @@ -30,7 +33,7 @@ def __call__(self, *args, **kwargs): merged_lst.append(merged_json) left_json = _pop_first(left_lst) right_json = _pop_first(right_lst) - elif left_json["id"] < right_json["id"]: + elif self.comparator(left_json, right_json) < 0: merged_lst.append(left_json) left_json = _pop_first(left_lst) else: @@ -41,7 +44,7 @@ def __call__(self, *args, **kwargs): return merged_lst def description(self): - return "LeftMergeById" + return "LeftMerge" def _pop_first(lst: list): diff --git a/api/dfg/util/Mapping.py b/api/dfg/util/Mapping.py new file mode 100644 index 0000000..95539e3 --- /dev/null +++ b/api/dfg/util/Mapping.py @@ -0,0 +1,20 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from typing import Callable, Any + +class Mapping(DfgNode): + + def __init__(self, child: DfgNode, fn: Callable[[Any, dict], DfgNode]): + self.child = child + self.fn = fn + + def __call__(self, *args, **kwargs) -> list: + result = [] + cache = {} + for ele in self.child(*args, **kwargs): + dfg = self.fn(ele, cache) + result.append(dfg(*args, **kwargs)) + return result + + def description(self): + return "Mapping" diff --git a/api/dfg/WaitTimeFilter.py b/api/dfg/wait_times/WaitTimeFilter.py similarity index 84% rename from api/dfg/WaitTimeFilter.py rename to api/dfg/wait_times/WaitTimeFilter.py index f88c8de..ae2bac0 100644 --- a/api/dfg/WaitTimeFilter.py +++ b/api/dfg/wait_times/WaitTimeFilter.py @@ -22,11 +22,7 @@ def __call__(self, *args, **kwargs): for day_wait_times in eatery.wait_times: filtered_data = [] for wait_time_data in day_wait_times.data: - eatery_events = eatery.events( - tzinfo=kwargs.get("tzinfo"), - start=kwargs.get("start"), - end=kwargs.get("end") - ) + eatery_events = eatery.events() if any([wait_time_data.timestamp in event for event in eatery_events]): filtered_data.append(wait_time_data) wait_times_filtered.append(WaitTimesDay( diff --git a/api/dfg/WaitTimes.py b/api/dfg/wait_times/WaitTimes.py similarity index 64% rename from api/dfg/WaitTimes.py rename to api/dfg/wait_times/WaitTimes.py index f91d84f..fd11beb 100644 --- a/api/dfg/WaitTimes.py +++ b/api/dfg/wait_times/WaitTimes.py @@ -13,49 +13,34 @@ class WaitTimes(DfgNode): + def __init__(self, eatery_id: EateryID, cache): + self.eatery_id = eatery_id + self.cache = cache + def __call__(self, *args, **kwargs) -> list[Eatery]: - transactions_by_date = {} - date = kwargs.get("start") - while date <= kwargs.get("end"): - # We only calculate the wait times for this first day - transactions_on_date = {} - past_days = [] - for i in range(1, 13): - # Look at the last 13 weeks, for each block_end_time for the same day of week, average together the - # transaction_count - past_day = date - timedelta(days=7 * i) - past_days.append(past_day) - transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ - .values("eatery_id", "block_end_time") \ - .annotate(transaction_avg=Avg("transaction_count")) - for unit in transaction_avg_counts: - transaction_history = TransactionHistorySerializer(unit) - if transaction_history.data['eatery_id'] != 0: - eatery_id = EateryID(transaction_history.data['eatery_id']) - if eatery_id not in transactions_on_date: - transactions_on_date[eatery_id] = [] - transactions_on_date[eatery_id].append(transaction_history) - transactions_by_date[date] = transactions_on_date - date += timedelta(days=1) - eateries = [] - for eatery_id in EateryID: - eatery_wait_times_by_day = [] - for date in transactions_by_date: - transactions = [] - if eatery_id in transactions_by_date[date]: - transactions = transactions_by_date[date][eatery_id] - eatery_wait_times_by_day.append(WaitTimes.generate_eatery_wait_times_by_day( - eatery_id, - date, - transactions - )) - eateries.append( - Eatery( - id=eatery_id, - wait_times=eatery_wait_times_by_day - ) - ) - return eateries + print(self.eatery_id) + if "transactions" not in self.cache: + transactions = {} + date = kwargs.get("start") + while date <= kwargs.get("end"): + transactions[date] = [] + past_days = [] + for i in range(1, 13): + past_days.append(date - timedelta(days=7*i)) + transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ + .values("eatery_id", "block_end_time") \ + .annotate(transaction_avg=Avg("transaction_count")) + for unit in transaction_avg_counts: + transactions[date].append(TransactionHistorySerializer(unit)) + date += timedelta(days=1) + self.cache["transactions"] = transactions + + eatery_wait_times = [] + for date in self.cache["transactions"]: + eatery_transaction_avgs = [transaction_avg for transaction_avg in self.cache["transactions"][date] if transaction_avg.data["eatery_id"] == self.eatery_id.value] + eatery_wait_times.append(WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs)) + + return eatery_wait_times # Expected amount of time (in seconds) for the length of the line to decrease by 1 person # Returns [lower, expected, upper] @@ -100,10 +85,7 @@ def generate_eatery_wait_times_by_day( base_times = WaitTimes.base_time_to_get_food(eatery_id) line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago - how_long_ago_guest_arrival = ( - base_times[1] - + line_decrease_times[1] * transactions[index].data["transaction_avg"] - ) + how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) if prev_bucket_guest_arrival > 9: # TODO: Send a slack error here instead diff --git a/api/util.py b/api/util.py new file mode 100644 index 0000000..305e3e5 --- /dev/null +++ b/api/util.py @@ -0,0 +1,71 @@ +from api.datatype.Eatery import EateryID + +CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" + +def dining_id_to_internal_id(id: int): + if id == 31: + return EateryID.ONE_ZERO_FOUR_WEST + elif id == 7: + return EateryID.LIBE_CAFE + elif id == 8: + return EateryID.ATRIUM_CAFE + elif id == 1: + return EateryID.BEAR_NECESSITIES + elif id == 25: + return EateryID.BECKER_HOUSE + elif id == 10: + return EateryID.BIG_RED_BARN + elif id == 11: + return EateryID.BUS_STOP_BAGELS + elif id == 12: + return EateryID.CAFE_JENNIE + elif id == 2: + return EateryID.CAROLS_CAFE + elif id == 26: + return EateryID.COOK_HOUSE + elif id == 14: + return EateryID.DAIRY_BAR + elif id == 41: + return EateryID.CROSSINGS_CAFE + elif id == 32: + return EateryID.FRANNYS + elif id == 16: + return EateryID.GOLDIES_CAFE + elif id == 15: + return EateryID.GREEN_DRAGON + elif id == 24: + return EateryID.HOT_DOG_CART + elif id == 34: + return EateryID.ICE_CREAM_BIKE + elif id == 27: + return EateryID.BETHE_HOUSE + elif id == 28: + return EateryID.JANSENS_MARKET + elif id == 29: + return EateryID.KEETON_HOUSE + elif id == 42: + return EateryID.MANN_CAFE + elif id == 18: + return EateryID.MARTHAS_CAFE + elif id == 19: + return EateryID.MATTINS_CAFE + elif id == 33: + return EateryID.MCCORMICKS + elif id == 3: + return EateryID.NORTH_STAR_DINING + elif id == 20: + return EateryID.OKENSHIELDS + elif id == 4: + return EateryID.RISLEY + elif id == 5: + return EateryID.RPCC + elif id == 30: + return EateryID.ROSE_HOUSE + elif id == 21: + return EateryID.RUSTYS + elif id == 13: + return EateryID.STRAIGHT_FROM_THE_MARKET + elif id == 23: + return EateryID.TRILLIUM + else: + return None \ No newline at end of file diff --git a/api/views.py b/api/views.py index e9345ed..7a68d8f 100644 --- a/api/views.py +++ b/api/views.py @@ -2,29 +2,50 @@ import pytz from django.http import JsonResponse +from api.datatype.Eatery import Eatery from api.dfg.CornellDiningNow import CornellDiningNow from api.dfg.EateryStubs import EateryStubs from api.dfg.ExternalEateries import ExternalEateries +from api.dfg.macros.EateryEvents import EateryEvents + +from api.dfg.util.DictResponseWrapper import DictResponseWrapper +from api.dfg.util.ConvertToJson import ConvertToJson +from api.dfg.util.EateryGenerator import EateryGenerator +from api.dfg.util.InMemoryCache import InMemoryCache +from api.dfg.util.Mapping import Mapping -from api.dfg.DictResponseWrapper import DictResponseWrapper -from api.dfg.EateryToJson import EateryToJson -from api.dfg.InMemoryCache import InMemoryCache from api.dfg.macros.LeftMergeEateries import LeftMergeEateries -from api.dfg.WaitTimes import WaitTimes -from api.dfg.WaitTimeFilter import WaitTimeFilter -dataflow_graph = DictResponseWrapper( - EateryToJson( +from api.dfg.wait_times.WaitTimes import WaitTimes +from api.dfg.wait_times.WaitTimeFilter import WaitTimeFilter + +main_dfg = DictResponseWrapper( + ConvertToJson( InMemoryCache( WaitTimeFilter( LeftMergeEateries( - WaitTimes(), + Mapping( + child=EateryStubs(), + fn = lambda eatery, cache: EateryGenerator( + eatery_id=eatery.id, + wait_times_dfg=WaitTimes(eatery.id, cache) + ) + ), LeftMergeEateries( - ExternalEateries(), + Mapping( + child = EateryStubs(), + fn = lambda eatery, cache: EateryGenerator( + eatery_id=eatery.id, + events_dfg=EateryEvents(eatery.id, cache) + ) + ), LeftMergeEateries( - CornellDiningNow(), - EateryStubs() + ExternalEateries(), + LeftMergeEateries( + CornellDiningNow(), + EateryStubs() + ) ) ) ) @@ -38,7 +59,7 @@ def index(request): tzinfo = pytz.timezone("US/Eastern") reload = request.GET.get('reload') - result = dataflow_graph( + result = main_dfg( tzinfo=tzinfo, reload=reload is not None and reload != "false", start=date.today(), diff --git a/eateries/admin.py b/eateries/admin.py index 2ca15c0..128f8c5 100644 --- a/eateries/admin.py +++ b/eateries/admin.py @@ -1,13 +1,16 @@ from django.contrib import admin -from eateries.models import EateryStore, MenuItemStore, MenuSubItemStore, MenuStore, ExceptionStore, ForSale, RepeatingEventSchedule, EventChangeLog +from eateries.models import EateryStore, ItemStore, SubItemStore, CategoryStore, MenuStore, ExceptionStore, CategoryItemAssociation, EventSchedule, DayOfWeekEventSchedule, DateEventSchedule, ClosedEventSchedule # Register your models here. admin.site.register(EateryStore) admin.site.register(MenuStore) -admin.site.register(MenuItemStore) -admin.site.register(MenuSubItemStore) +admin.site.register(ItemStore) +admin.site.register(SubItemStore) admin.site.register(ExceptionStore) -admin.site.register(ForSale) -admin.site.register(RepeatingEventSchedule) -admin.site.register(EventChangeLog) \ No newline at end of file +admin.site.register(CategoryStore) +admin.site.register(CategoryItemAssociation) +admin.site.register(EventSchedule) +admin.site.register(DayOfWeekEventSchedule) +admin.site.register(DateEventSchedule) +admin.site.register(ClosedEventSchedule) \ No newline at end of file diff --git a/eateries/migrations/0001_initial.py b/eateries/migrations/0001_initial.py index bd272ef..8153937 100644 --- a/eateries/migrations/0001_initial.py +++ b/eateries/migrations/0001_initial.py @@ -1,6 +1,5 @@ -# Generated by Django 4.0 on 2022-01-06 14:51 +# Generated by Django 4.0 on 2022-01-07 18:50 -import datetime from django.db import migrations, models import django.db.models.deletion @@ -17,89 +16,107 @@ class Migration(migrations.Migration): name='EateryStore', fields=[ ('id', models.IntegerField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=40)), - ('menu_summary', models.CharField(max_length=60)), - ('image_url', models.URLField()), - ('location', models.CharField(max_length=30)), - ('campus_area', models.CharField(choices=[('WST', 'West'), ('NRTH', 'North'), ('CNTRL', 'Central'), ('CTWN', 'Collegetown'), ('NN', '')], default='NN', max_length=5)), - ('online_order', models.BooleanField(null=True)), - ('online_order_url', models.URLField()), - ('latitude', models.FloatField(null=True)), - ('longitude', models.FloatField(null=True)), - ('payment_accepts_meal_swipes', models.BooleanField(null=True)), - ('payment_accepts_brbs', models.BooleanField(null=True)), - ('payment_accepts_cash', models.BooleanField(null=True)), + ('name', models.CharField(blank=True, max_length=40)), + ('menu_summary', models.CharField(blank=True, max_length=60)), + ('image_url', models.URLField(blank=True)), + ('location', models.CharField(blank=True, max_length=30)), + ('campus_area', models.CharField(blank=True, choices=[('WST', 'West'), ('NRTH', 'North'), ('CNTRL', 'Central'), ('CTWN', 'Collegetown'), ('NN', '')], default='NN', max_length=5)), + ('online_order', models.BooleanField(blank=True, null=True)), + ('online_order_url', models.URLField(blank=True)), + ('latitude', models.FloatField(blank=True, null=True)), + ('longitude', models.FloatField(blank=True, null=True)), + ('payment_accepts_meal_swipes', models.BooleanField(blank=True, null=True)), + ('payment_accepts_brbs', models.BooleanField(blank=True, null=True)), + ('payment_accepts_cash', models.BooleanField(blank=True, null=True)), ], ), migrations.CreateModel( - name='MenuStore', + name='EventSchedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.CreateModel( + name='ItemStore', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=40)), + ('description', models.CharField(blank=True, max_length=200)), + ('base_price', models.FloatField(blank=True, null=True)), ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), ], ), migrations.CreateModel( - name='RepeatingEventSchedule', + name='ClosedEventSchedule', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('eventschedule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='eateries.eventschedule')), + ('canonical_date', models.DateField()), + ], + bases=('eateries.eventschedule',), + ), + migrations.CreateModel( + name='DateEventSchedule', + fields=[ + ('eventschedule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='eateries.eventschedule')), + ('canonical_date', models.DateField()), + ('start_timestamp', models.DateTimeField()), + ('end_timestamp', models.DateTimeField()), + ], + bases=('eateries.eventschedule',), + ), + migrations.CreateModel( + name='DayOfWeekEventSchedule', + fields=[ + ('eventschedule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='eateries.eventschedule')), ('day_of_week', models.CharField(choices=[('MON', 'Monday'), ('TUE', 'Tuesday'), ('WED', 'Wednesday'), ('THU', 'Thursday'), ('FRI', 'Friday'), ('SAT', 'Saturday'), ('SUN', 'Sunday')], max_length=3)), - ('event_description', models.CharField(choices=[('BRKFST', 'Breakfast'), ('LNCH', 'Lunch'), ('DNNR', 'Dinner'), ('GNRL', 'General')], max_length=10)), ('start', models.TimeField()), ('end', models.TimeField()), - ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), ], + bases=('eateries.eventschedule',), ), migrations.CreateModel( - name='MenuItemStore', + name='SubItemStore', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('additional_price', models.FloatField(blank=True, null=True)), + ('total_price', models.FloatField(blank=True, null=True)), ('name', models.CharField(max_length=40)), - ('description', models.CharField(max_length=50)), - ('price', models.FloatField(null=True)), - ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('item_subsection', models.CharField(max_length=40)), + ('item_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), ], ), migrations.CreateModel( - name='ForSale', + name='MenuStore', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('category', models.CharField(max_length=40)), - ('item_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menuitemstore')), - ('menu_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ('name', models.CharField(max_length=40)), + ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), ], ), migrations.CreateModel( name='ExceptionStore', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('description', models.CharField(max_length=80)), + ('description', models.CharField(max_length=250)), ('start_timestamp', models.DateTimeField()), ('end_timestamp', models.DateTimeField()), ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), ], ), migrations.CreateModel( - name='EventChangeLog', + name='CategoryStore', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(default=datetime.datetime.now)), - ('type', models.CharField(choices=[('INS', 'Insert'), ('DEL', 'Delete')], max_length=3)), - ('event_description', models.CharField(choices=[('BRKFST', 'Breakfast'), ('LNCH', 'Lunch'), ('DNNR', 'Dinner'), ('GNRL', 'General')], max_length=10)), - ('canonical_date', models.DateField()), - ('start', models.DateTimeField()), - ('end', models.DateTimeField()), - ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ('category', models.CharField(max_length=40)), + ('menu_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), ], ), - migrations.AddIndex( - model_name='eventchangelog', - index=models.Index(fields=['eatery_id', 'canonical_date'], name='eateries_ev_eatery__d34bcb_idx'), - ), - migrations.AddIndex( - model_name='eventchangelog', - index=models.Index(fields=['created_at'], name='eateries_ev_created_d237e2_idx'), + migrations.CreateModel( + name='CategoryItemAssociation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.categorystore')), + ('item_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), + ], ), ] diff --git a/eateries/migrations/0002_alter_eaterystore_campus_area_and_more.py b/eateries/migrations/0002_alter_eaterystore_campus_area_and_more.py deleted file mode 100644 index 23a0cb7..0000000 --- a/eateries/migrations/0002_alter_eaterystore_campus_area_and_more.py +++ /dev/null @@ -1,73 +0,0 @@ -# Generated by Django 4.0 on 2022-01-06 15:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='eaterystore', - name='campus_area', - field=models.CharField(blank=True, choices=[('WST', 'West'), ('NRTH', 'North'), ('CNTRL', 'Central'), ('CTWN', 'Collegetown'), ('NN', '')], default='NN', max_length=5), - ), - migrations.AlterField( - model_name='eaterystore', - name='image_url', - field=models.URLField(blank=True), - ), - migrations.AlterField( - model_name='eaterystore', - name='latitude', - field=models.FloatField(blank=True, null=True), - ), - migrations.AlterField( - model_name='eaterystore', - name='location', - field=models.CharField(blank=True, max_length=30), - ), - migrations.AlterField( - model_name='eaterystore', - name='longitude', - field=models.FloatField(blank=True, null=True), - ), - migrations.AlterField( - model_name='eaterystore', - name='menu_summary', - field=models.CharField(blank=True, max_length=60), - ), - migrations.AlterField( - model_name='eaterystore', - name='name', - field=models.CharField(blank=True, max_length=40), - ), - migrations.AlterField( - model_name='eaterystore', - name='online_order', - field=models.BooleanField(blank=True, null=True), - ), - migrations.AlterField( - model_name='eaterystore', - name='online_order_url', - field=models.URLField(blank=True), - ), - migrations.AlterField( - model_name='eaterystore', - name='payment_accepts_brbs', - field=models.BooleanField(blank=True, null=True), - ), - migrations.AlterField( - model_name='eaterystore', - name='payment_accepts_cash', - field=models.BooleanField(blank=True, null=True), - ), - migrations.AlterField( - model_name='eaterystore', - name='payment_accepts_meal_swipes', - field=models.BooleanField(blank=True, null=True), - ), - ] diff --git a/eateries/migrations/0003_rename_price_menuitemstore_base_price_and_more.py b/eateries/migrations/0003_rename_price_menuitemstore_base_price_and_more.py deleted file mode 100644 index ad19a71..0000000 --- a/eateries/migrations/0003_rename_price_menuitemstore_base_price_and_more.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 4.0 on 2022-01-06 16:05 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0002_alter_eaterystore_campus_area_and_more'), - ] - - operations = [ - migrations.RenameField( - model_name='menuitemstore', - old_name='price', - new_name='base_price', - ), - migrations.CreateModel( - name='MenuSubItemStore', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('additional_price', models.FloatField(null=True)), - ('total_price', models.FloatField(null=True)), - ('name', models.CharField(max_length=40)), - ('item_subsection', models.CharField(max_length=40)), - ('item_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menuitemstore')), - ], - ), - ] diff --git a/eateries/migrations/0004_alter_menuitemstore_description.py b/eateries/migrations/0004_alter_menuitemstore_description.py deleted file mode 100644 index b31a71e..0000000 --- a/eateries/migrations/0004_alter_menuitemstore_description.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0 on 2022-01-06 16:07 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0003_rename_price_menuitemstore_base_price_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='menuitemstore', - name='description', - field=models.CharField(blank=True, max_length=50), - ), - ] diff --git a/eateries/migrations/0005_alter_menuitemstore_base_price_and_more.py b/eateries/migrations/0005_alter_menuitemstore_base_price_and_more.py deleted file mode 100644 index e11ace2..0000000 --- a/eateries/migrations/0005_alter_menuitemstore_base_price_and_more.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 4.0 on 2022-01-06 16:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0004_alter_menuitemstore_description'), - ] - - operations = [ - migrations.AlterField( - model_name='menuitemstore', - name='base_price', - field=models.FloatField(blank=True, null=True), - ), - migrations.AlterField( - model_name='menusubitemstore', - name='additional_price', - field=models.FloatField(blank=True, null=True), - ), - migrations.AlterField( - model_name='menusubitemstore', - name='total_price', - field=models.FloatField(blank=True, null=True), - ), - ] diff --git a/eateries/migrations/0006_alter_exceptionstore_description_and_more.py b/eateries/migrations/0006_alter_exceptionstore_description_and_more.py deleted file mode 100644 index f707e9f..0000000 --- a/eateries/migrations/0006_alter_exceptionstore_description_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.0 on 2022-01-06 16:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0005_alter_menuitemstore_base_price_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='exceptionstore', - name='description', - field=models.CharField(max_length=100), - ), - migrations.AlterField( - model_name='menuitemstore', - name='description', - field=models.CharField(blank=True, max_length=100), - ), - ] diff --git a/eateries/models.py b/eateries/models.py index 79ed055..725789f 100644 --- a/eateries/models.py +++ b/eateries/models.py @@ -24,40 +24,48 @@ class CampusArea(models.TextChoices): class ExceptionStore(models.Model): eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - description = models.CharField(max_length = 100) + description = models.CharField(max_length = 250) start_timestamp = models.DateTimeField() end_timestamp = models.DateTimeField() -class MenuItemStore(models.Model): +class MenuStore(models.Model): + eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + name = models.CharField(max_length = 40) + +class CategoryStore(models.Model): + menu_id = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + category = models.CharField(max_length = 40) + +class ItemStore(models.Model): eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) name = models.CharField(max_length=40) - description = models.CharField(max_length = 100, blank=True) + description = models.CharField(max_length = 200, blank=True) base_price = models.FloatField(null = True, blank=True) -class MenuSubItemStore(models.Model): - item_id = models.ForeignKey(MenuItemStore, on_delete=models.DO_NOTHING) +class SubItemStore(models.Model): + item_id = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) additional_price = models.FloatField(null = True, blank=True) total_price = models.FloatField(null = True, blank=True) name = models.CharField(max_length=40) item_subsection = models.CharField(max_length=40) +class CategoryItemAssociation(models.Model): + item_id = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) + category_id = models.ForeignKey(CategoryStore, on_delete=models.DO_NOTHING) -class MenuStore(models.Model): - eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - name = models.CharField(max_length = 40) - -class ForSale(models.Model): - item_id = models.ForeignKey(MenuItemStore, on_delete=models.DO_NOTHING) - menu_id = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - category = models.CharField(max_length=40) class EventDescription(models.TextChoices): BREAKFAST = 'BRKFST' + BRUNCH = 'BRNCH' LUNCH = 'LNCH' DINNER = 'DNNR' GENERAL = 'GNRL' -class RepeatingEventSchedule(models.Model): +class EventSchedule(models.Model): + eatery_id: models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + +class DayOfWeekEventSchedule(EventSchedule): + class DayOfTheWeek(models.TextChoices): MONDAY = 'MON' TUESDAY = 'TUE' @@ -66,32 +74,22 @@ class DayOfTheWeek(models.TextChoices): FRIDAY = 'FRI' SATURDAY = 'SAT' SUNDAY = 'SUN' - - eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + + event_description: models.CharField(choices=EventDescription.choices, max_length = 10) + menu: models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=3) - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) start = models.TimeField() end = models.TimeField() -class EventChangeLog(models.Model): - class Meta: - indexes = [ - models.Index(fields = ['eatery_id', 'canonical_date']), - models.Index(fields = ['created_at']) - ] - class ChangeLogType(models.TextChoices): - INSERT = 'INS' - DELETE = 'DEL' - - eatery_id = models.ForeignKey(EateryStore, on_delete = models.DO_NOTHING) - menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - created_at = models.DateTimeField(default=datetime.now) - type = models.CharField(choices=ChangeLogType.choices, max_length=3) - event_description = models.CharField(choices=EventDescription.choices, max_length= 10) +class DateEventSchedule(EventSchedule): + event_description: models.CharField(choices=EventDescription.choices, max_length = 10) + menu: models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) canonical_date = models.DateField() - start = models.DateTimeField() - end = models.DateTimeField() + start_timestamp = models.DateTimeField() + end_timestamp = models.DateTimeField() +class ClosedEventSchedule(EventSchedule): + event_description: models.CharField(choices=EventDescription.choices, max_length = 10) + canonical_date = models.DateField() diff --git a/transactions/management/commands/ingest_log_transactions.py b/transactions/management/commands/ingest_log_transactions.py new file mode 100644 index 0000000..6d14b93 --- /dev/null +++ b/transactions/management/commands/ingest_log_transactions.py @@ -0,0 +1,32 @@ +# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db + +import json +from datetime import datetime +from django.core.management.base import BaseCommand + +from transactions.controllers.update_transactions_controller import UpdateTransactionsController +from transactions.models import TransactionHistory + +class Command(BaseCommand): + help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistory table' + + def handle(self, *args, **options): + num_deleted = TransactionHistory.objects.all().delete()[0] + counter = 0 + num_inserted = 0 + with open("static_sources/data.log", "r") as log: + for line in log: + try: + data = json.loads(line) + timestamp = datetime.strptime(data['TIMESTAMP'], '%Y-%m-%d %I:%M:%S %p') + if counter % 100 == 1: + print(timestamp) + if timestamp.year == 2021 and timestamp.month > 7: + counter += 1 + res = UpdateTransactionsController(data).process() + if res["success"]: + num_inserted += res["result"]["num_inserted"] + except Exception as e: + pass + print("{} Entries Deleted".format(num_deleted)) + print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file From bfc6d537116ed6a434e2054a0a811d1a0726fcc3 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Fri, 7 Jan 2022 17:25:42 -0500 Subject: [PATCH 033/305] Add rest_framework to installed apps --- eatery_blue_backend/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eatery_blue_backend/settings.py b/eatery_blue_backend/settings.py index 1e806a6..4313215 100644 --- a/eatery_blue_backend/settings.py +++ b/eatery_blue_backend/settings.py @@ -38,7 +38,8 @@ "django.contrib.messages", "django.contrib.staticfiles", "transactions", - "eateries" + "eateries", + "rest_framework" ] MIDDLEWARE = [ From 8c0ec385f0af056577e105cfd03bae9dc25e8b51 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Fri, 7 Jan 2022 17:56:24 -0500 Subject: [PATCH 034/305] Remove wait times --- api/dfg/wait_times/WaitTimes.py | 122 ++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 api/dfg/wait_times/WaitTimes.py diff --git a/api/dfg/wait_times/WaitTimes.py b/api/dfg/wait_times/WaitTimes.py new file mode 100644 index 0000000..a6bba92 --- /dev/null +++ b/api/dfg/wait_times/WaitTimes.py @@ -0,0 +1,122 @@ +from datetime import date, datetime, timedelta + +import pytz +from django.db.models import Avg + +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.WaitTime import WaitTime +from api.datatype.WaitTimesDay import WaitTimesDay +from api.dfg.DfgNode import DfgNode +from transactions.models import TransactionHistory +from transactions.serializers import TransactionHistorySerializer + + +class WaitTimes(DfgNode): + + def __init__(self, eatery_id: EateryID, cache): + self.eatery_id = eatery_id + self.cache = cache + + def __call__(self, *args, **kwargs) -> list[Eatery]: + if "transactions" not in self.cache: + transactions = {} + date = kwargs.get("start") + while date <= kwargs.get("end"): + transactions[date] = [] + past_days = [] + for i in range(1, 13): + past_days.append(date - timedelta(days=7*i)) + transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ + .values("eatery_id", "block_end_time") \ + .annotate(transaction_avg=Avg("transaction_count")) + for unit in transaction_avg_counts: + transactions[date].append(TransactionHistorySerializer(unit)) + date += timedelta(days=1) + self.cache["transactions"] = transactions + + eatery_wait_times = [] + for date in self.cache["transactions"]: + eatery_transaction_avgs = [transaction_avg for transaction_avg in self.cache["transactions"][date] if transaction_avg.data["eatery_id"] == self.eatery_id.value] + eatery_wait_times.append(WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs)) + + return eatery_wait_times + + # Expected amount of time (in seconds) for the length of the line to decrease by 1 person + # Returns [lower, expected, upper] + @staticmethod + def line_decrease_by_one_time(eatery_id: EateryID) -> list[int]: + if eatery_id == EateryID.MACS_CAFE: + return [24, 27, 30] + elif eatery_id == EateryID.MATTINS_CAFE: + return [9, 15, 21] + elif eatery_id == EateryID.TERRACE: + return [15, 27, 36] + elif eatery_id == EateryID.OKENSHIELDS: + return [4, 8, 12] + else: + return [18, 21, 24] + + # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the + # amount of time to check out Returns [lower, expected, upper] + @staticmethod + def base_time_to_get_food(eatery_id: EateryID) -> list[int]: + if eatery_id == EateryID.MACS_CAFE: + return [240, 300, 360] + elif eatery_id == EateryID.MATTINS_CAFE: + return [150, 210, 270] + elif eatery_id == EateryID.TERRACE: + return [180, 300, 420] + elif eatery_id == EateryID.OKENSHIELDS: + return [80, 120, 180] + else: + return [180, 240, 300] + + @staticmethod + def generate_eatery_wait_times_by_day( + eatery_id: EateryID, + date: date, + transactions: list[TransactionHistorySerializer] + ) -> WaitTimesDay: + + wait_times_data = [] + customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + for index in reversed(range(0, len(transactions))): + base_times = WaitTimes.base_time_to_get_food(eatery_id) + line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) + # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago + how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] + prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) + if prev_bucket_guest_arrival > 9: + # TODO: Send a slack error here instead + print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") + else: + customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index].data["transaction_avg"] + num_customers = customers_waiting_in_line.pop(0) + wait_time_low = int(base_times[0] + line_decrease_times[0] * num_customers) + wait_time_expected = int(base_times[1] + line_decrease_times[1] * num_customers) + wait_time_high = int(base_times[2] + line_decrease_times[2] * num_customers) + + customers_waiting_in_line.append(0.0) + block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() + timestamp = int(WaitTimes.timestamp_combined(date, block_end_time) - 5 * 60 / 2) + wait_times_data.insert(0, WaitTime( + timestamp=timestamp, + wait_time_low=wait_time_low, + wait_time_expected=wait_time_expected, + wait_time_high=wait_time_high + )) + + return WaitTimesDay(canonical_date=date, data=wait_times_data) + + @staticmethod + def timestamp_combined(date: datetime.date, time: datetime.time): + """ + Returns the Unix (UTC) timestamp of the combined (date, time) in the + New York timezone. + """ + + tz = pytz.timezone('America/New_York') + return int(tz.localize(datetime.combine(date, time)).timestamp()) + + def description(self): + return "WaitTimes" From a653c081c3509e58c2277be1a65134ca060d5c80 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Sat, 8 Jan 2022 09:54:15 -0500 Subject: [PATCH 035/305] Move allowed hosts to env vars --- .gitignore | 3 ++- eatery_blue_backend/settings.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 513b7e9..ee8e727 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ env/ .env +eatery-dev.pem # Byte-compiled / optimized / DLL files __pycache__/ @@ -48,4 +49,4 @@ cover/ *.log local_settings.py db.sqlite3 -db.sqlite3-journal \ No newline at end of file +db.sqlite3-journal diff --git a/eatery_blue_backend/settings.py b/eatery_blue_backend/settings.py index 4313215..f63a119 100644 --- a/eatery_blue_backend/settings.py +++ b/eatery_blue_backend/settings.py @@ -11,6 +11,7 @@ """ from pathlib import Path +import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -25,7 +26,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ["d706-2601-187-8400-2076-1158-3d93-7b45-1456.ngrok.io", "127.0.0.1", "localhost"] +ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(",") # Application definition From 23c7b98359bc9644b523ccbab0ce017b454f7131 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 10 Jan 2022 13:33:07 -0500 Subject: [PATCH 036/305] Add menu_summary, image_url, exceptions --- .gitignore | 27 +-- api/datatype/Eatery.py | 41 ++-- api/datatype/EateryException.py | 30 +++ api/datatype/Menu.py | 1 - api/dfg/EateriesFromDB.py | 80 ++++++++ api/dfg/ExternalEateries.py | 179 ------------------ api/dfg/schedule/ClosedSchedule.py | 4 +- api/dfg/schedule/DateSchedule.py | 4 +- api/dfg/schedule/DayOfWeekSchedule.py | 4 +- api/dfg/wait_times/WaitTimes.py | 14 +- api/models.py | 3 - api/views.py | 4 +- eateries/admin.py | 23 ++- .../management/commands/create_db_snapshot.py | 60 ++++++ .../management/commands/ingest_db_snapshot.py | 42 ++++ eateries/migrations/0001_initial.py | 56 ++---- .../0002_closedeventschedule_and_more.py | 27 +++ ...003_closedeventschedule_eatery_and_more.py | 26 +++ ...ayofweekeventschedule_dateeventschedule.py | 40 ++++ ...ventschedule_event_description_and_more.py | 38 ++++ ...temassociation_unique_together_and_more.py | 70 +++++++ eateries/models.py | 91 +++++---- eateries/scripts/create_db_snapshot.py | 9 - eateries/scripts/ingest_db_snpashot.py | 0 eateries/serializers.py | 38 ++++ eateries/util.py | 17 ++ .../update_transactions_controller.py | 18 +- .../commands/ingest_log_transactions.py | 6 +- transactions/migrations/0001_initial.py | 17 +- ...p_transactionhistory_timestamp_and_more.py | 30 --- ...ry_transaction_name_204126_idx_and_more.py | 21 -- ...nsactionhistory_block_end_time_and_more.py | 22 --- ...sactionhistory_bucket_end_time_and_more.py | 22 --- ...lter_transactionhistory_bucket_end_time.py | 18 -- ...lter_transactionhistory_bucket_end_time.py | 18 -- ...nsactionhistory_block_end_time_and_more.py | 22 --- ...sactionhistory_unique_together_and_more.py | 31 --- transactions/models.py | 4 +- transactions/serializers.py | 25 --- 39 files changed, 613 insertions(+), 569 deletions(-) create mode 100644 api/datatype/EateryException.py create mode 100644 api/dfg/EateriesFromDB.py delete mode 100644 api/dfg/ExternalEateries.py delete mode 100644 api/models.py create mode 100644 eateries/management/commands/create_db_snapshot.py create mode 100644 eateries/management/commands/ingest_db_snapshot.py create mode 100644 eateries/migrations/0002_closedeventschedule_and_more.py create mode 100644 eateries/migrations/0003_closedeventschedule_eatery_and_more.py create mode 100644 eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py create mode 100644 eateries/migrations/0005_dateeventschedule_event_description_and_more.py create mode 100644 eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py delete mode 100644 eateries/scripts/create_db_snapshot.py delete mode 100644 eateries/scripts/ingest_db_snpashot.py create mode 100644 eateries/serializers.py create mode 100644 eateries/util.py delete mode 100644 transactions/migrations/0002_rename_end_timestamp_transactionhistory_timestamp_and_more.py delete mode 100644 transactions/migrations/0003_remove_transactionhistory_transaction_name_204126_idx_and_more.py delete mode 100644 transactions/migrations/0004_rename_timestamp_transactionhistory_block_end_time_and_more.py delete mode 100644 transactions/migrations/0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more.py delete mode 100644 transactions/migrations/0006_alter_transactionhistory_bucket_end_time.py delete mode 100644 transactions/migrations/0007_alter_transactionhistory_bucket_end_time.py delete mode 100644 transactions/migrations/0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more.py delete mode 100644 transactions/migrations/0009_alter_transactionhistory_unique_together_and_more.py delete mode 100644 transactions/serializers.py diff --git a/.gitignore b/.gitignore index ee8e727..81bcab5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,18 @@ -env/ +# Security .env eatery-dev.pem +# Hardcoded stuff +db_snapshots/ +static_sources/data.log + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class -# C extensions -*.so - -# Distribution / packaging +# More Python stuff +env/ .Python build/ develop-eggs/ @@ -30,21 +32,6 @@ share/python-wheels/ *.egg MANIFEST -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - # Django stuff: *.log local_settings.py diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index adf9a8c..6deaaa0 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -3,6 +3,7 @@ from enum import Enum import pytz +from api.datatype.EateryException import EateryException from api.datatype.Event import Event, filter_range from api.datatype.WaitTimesDay import WaitTimesDay @@ -48,13 +49,14 @@ class EateryID(Enum): LOUIES = 37 ANABELS_GROCERY = 38 - class Eatery: def __init__( self, id: EateryID, name: Optional[str] = None, + image_url: Optional[str] = None, + menu_summary: Optional[str] = None, campus_area: Optional[str] = None, events: Optional[list[Event]] = None, latitude: Optional[float] = None, @@ -63,10 +65,13 @@ def __init__( location: Optional[str] = None, online_order: Optional[bool] = None, online_order_url: Optional[str] = None, - wait_times: Optional[list[WaitTimesDay]] = None + wait_times: Optional[list[WaitTimesDay]] = None, + exceptions: Optional[list[EateryException]] = None ): self.id = id self.name = name + self.image_url = image_url + self.menu_summary = menu_summary self.campus_area = campus_area self.latitude = latitude self.longitude = longitude @@ -76,6 +81,7 @@ def __init__( self.online_order = online_order self.online_order_url = online_order_url self.wait_times = wait_times + self.exceptions = exceptions def events( self, @@ -94,6 +100,8 @@ def to_json( eatery = { "id": self.id.value, "name": self.name, + "image_url": self.image_url, + "menu_summary": self.menu_summary, "campus_area": self.campus_area, "events": None if self.known_events is None else [event.to_json() for event in self.events(tzinfo, start, end)], @@ -104,26 +112,35 @@ def to_json( "location": self.location, "online_order": self.online_order, "online_order_url": self.online_order_url, - "wait_times": None if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times] + "wait_times": None if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times], + "exceptions": None if self.exceptions is None else [exception.to_json() for exception in self.exceptions] } return eatery + @staticmethod + def parse_field(json, key): + return None if key not in json else json[key] + @staticmethod def from_json(eatery_json): return Eatery( id=None if "id" not in eatery_json else EateryID(eatery_json["id"]), - name=None if "name" not in eatery_json else eatery_json["name"], - campus_area=None if "campus_area" not in eatery_json else eatery_json["campus_area"], + name=Eatery.parse_field(eatery_json, "name"), + image_url=Eatery.parse_field(eatery_json, "image_url"), + menu_summary=Eatery.parse_field(eatery_json, "menu_summary"), + campus_area=Eatery.parse_field(eatery_json, "campus_area"), events=None if "events" not in eatery_json or eatery_json["events"] is None else [Event.from_json(event) for event in eatery_json["events"]], - latitude=None if "latitude" not in eatery_json else eatery_json["latitude"], - longitude=None if "longitude" not in eatery_json else eatery_json["longitude"], - payment_methods=None if "payment_methods" not in eatery_json else eatery_json["payment_methods"], - location=None if "location" not in eatery_json else eatery_json["location"], - online_order=None if "online_order" not in eatery_json else eatery_json["online_order"], - online_order_url=None if "online_order_url" not in eatery_json else eatery_json["online_order_url"], + latitude=Eatery.parse_field(eatery_json, "latitude"), + longitude=Eatery.parse_field(eatery_json, "longitude"), + payment_methods=Eatery.parse_field(eatery_json, "payment_methods"), + location=Eatery.parse_field(eatery_json, "location"), + online_order=Eatery.parse_field(eatery_json, "online_order"), + online_order_url=Eatery.parse_field(eatery_json, "online_order_url"), wait_times=None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None - else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]] + else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]], + exceptions=None if "exceptions" not in eatery_json or eatery_json["exceptions"] is None + else [EateryException.from_json(exception) for exception in eatery_json["exceptions"]] ) def clone(self): diff --git a/api/datatype/EateryException.py b/api/datatype/EateryException.py new file mode 100644 index 0000000..656ee6d --- /dev/null +++ b/api/datatype/EateryException.py @@ -0,0 +1,30 @@ +class EateryException: + + def __init__( + self, + id: int, + description: str, + start_timestamp: int, + end_timestamp: int + ): + self.id = id + self.description = description + self.start_timestamp = start_timestamp + self.end_timestamp = end_timestamp + + def to_json(self): + return { + "id": 1, + "description": self.description, + "start_timestamp": self.start_timestamp, + "end_timestamp": self.end_timestamp + } + + @staticmethod + def from_json(exception_json): + return EateryException( + id = exception_json["id"], + description=exception_json["description"], + start_timestamp=exception_json["start_timestamp"], + end_timestamp=exception_json["end_timestamp"] + ) diff --git a/api/datatype/Menu.py b/api/datatype/Menu.py index c7505b0..c48e100 100644 --- a/api/datatype/Menu.py +++ b/api/datatype/Menu.py @@ -1,6 +1,5 @@ from api.datatype.MenuCategory import MenuCategory - class Menu: def __init__(self, categories: list[MenuCategory]): diff --git a/api/dfg/EateriesFromDB.py b/api/dfg/EateriesFromDB.py new file mode 100644 index 0000000..6402385 --- /dev/null +++ b/api/dfg/EateriesFromDB.py @@ -0,0 +1,80 @@ +import datetime +import json +import re + +import pytz + +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.EateryException import EateryException +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem +from api.dfg.DfgNode import DfgNode + +from eateries.models import EateryStore, ExceptionStore +from eateries.serializers import EateryStoreSerializer, ExceptionStoreSerializer + +from datetime import datetime + +# eventually need to deprecate this for a custom DB backend storing all of the overrides + +class EateriesFromDB(DfgNode): + + def __call__(self, *args, **kwargs) -> list[Eatery]: + eateries = EateryStore.objects.all() + serialized_eateries = EateryStoreSerializer(data=eateries, many=True) + serialized_eateries.is_valid() + exceptions = ExceptionStore.objects.filter(end_timestamp__gte=datetime.now().timestamp(), start_timestamp__lte=datetime.now().timestamp()) + serialized_exceptions = ExceptionStoreSerializer(data=exceptions, many=True) + serialized_exceptions.is_valid() + return list(map(lambda x: EateriesFromDB.eatery_from_serialized(x, serialized_exceptions.data), serialized_eateries.data)) + + @staticmethod + def none_repr(str): + return None if str == None or len(str) == 0 else str + + @staticmethod + def eatery_from_serialized(serialized_eatery: dict, serialized_exceptions: list[dict]) -> Eatery: + return Eatery( + id=EateryID(serialized_eatery["id"]), + name=EateriesFromDB.none_repr(serialized_eatery["name"]), + image_url=EateriesFromDB.none_repr(serialized_eatery["image_url"]), + menu_summary=EateriesFromDB.none_repr(serialized_eatery["menu_summary"]), + campus_area=EateriesFromDB.none_repr(serialized_eatery["campus_area"]), + events=None, + latitude=serialized_eatery["latitude"], + longitude=serialized_eatery["longitude"], + payment_methods=EateriesFromDB.payment_methods(serialized_eatery), + location=EateriesFromDB.none_repr(serialized_eatery["location"]), + online_order=serialized_eatery["online_order"], + online_order_url=EateriesFromDB.none_repr(serialized_eatery["online_order_url"]), + exceptions = EateriesFromDB.exceptions(serialized_eatery["id"], serialized_exceptions) + ) + + @staticmethod + def payment_methods(serialized_eatery:dict): + pay_methods = [] + if serialized_eatery["payment_accepts_cash"]: + pay_methods.append("cash") + if serialized_eatery["payment_accepts_brbs"]: + pay_methods.append("brbs") + if serialized_eatery["payment_accepts_meal_swipes"]: + pay_methods.append("swipes") + return pay_methods + + @staticmethod + def exceptions(eatery_id: int, serialized_exceptions: list[dict]): + return [EateriesFromDB.exception_from_serialized(exception) for exception in serialized_exceptions if exception["eatery"] == eatery_id] + + @staticmethod + def exception_from_serialized(serialized_exception: dict): + return EateryException( + id=serialized_exception["id"], + description=serialized_exception["description"], + start_timestamp=serialized_exception["start_timestamp"], + end_timestamp=serialized_exception["end_timestamp"] + ) + + + diff --git a/api/dfg/ExternalEateries.py b/api/dfg/ExternalEateries.py deleted file mode 100644 index edf9957..0000000 --- a/api/dfg/ExternalEateries.py +++ /dev/null @@ -1,179 +0,0 @@ -import datetime -import json -import re - -import pytz - -from api.datatype.Eatery import Eatery, EateryID -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem -from api.dfg.DfgNode import DfgNode - - -# eventually need to deprecate this for a custom DB backend storing all of the overrides - -class ExternalEateries(DfgNode): - # TODO: Make parsing of ExternalEateries the same as parsing of normal eateries, except from file, and then read - # external data on top of this - EXTERNAL_EATERIES_PATH = "static_sources/external_eateries.json" - - # based on date.weekday() - WEEKDAYS = [ - 'monday', - 'tuesday', - 'wednesday', - 'thursday', - 'friday', - 'saturday', - 'sunday' - ] - - def __call__(self, *args, **kwargs) -> list[Eatery]: - eateries = [] - - with open(ExternalEateries.EXTERNAL_EATERIES_PATH) as f: - json_eateries = json.load(f)["eateries"] - - start = kwargs.get("start", datetime.date.today()) - end = kwargs.get("end", start + datetime.timedelta(days=7)) - - for json_eatery in json_eateries: - eateries.append(ExternalEateries.eatery_from_json( - json_eatery, - start=start, - end=end - )) - - return eateries - - @staticmethod - def eatery_from_json(json_eatery: dict, start: datetime.date, end: datetime.date) -> Eatery: - return Eatery( - id=EateryID(json_eatery["id"]), - name=json_eatery["name"], - campus_area=json_eatery["campusArea"]["descrshort"], - events=ExternalEateries.eatery_events_from_json( - json_operating_hours=json_eatery["operatingHours"], - json_dates_closed=json_eatery["datesClosed"], - json_dining_items=json_eatery["diningItems"], - start_date=start, - end_date=end, - ), - latitude=json_eatery["coordinates"]["latitude"], - longitude=json_eatery["coordinates"]["longitude"], - payment_methods=ExternalEateries.generate_payment_methods(json_eatery["payMethods"]), - location=json_eatery["location"], - online_order=json_eatery["onlineOrdering"], - online_order_url=json_eatery["onlineOrderUrl"] - ) - - @staticmethod - def generate_payment_methods(json_paymethods: list): - payment_methods = [] - takes_cash = True - takes_brbs = any([method["descrshort"] == "Meal Plan - Debit" for method in json_paymethods]) - takes_swipes = any([method["descrshort"] == "Meal Plan - Swipe" for method in json_paymethods]) - if takes_cash: - payment_methods.append("cash") - if takes_brbs: - payment_methods.append("brbs") - if takes_swipes: - payment_methods.append("swipes") - return payment_methods - - @staticmethod - def eatery_events_from_json( - json_operating_hours: list, - json_dates_closed: list, - json_dining_items: list, - start_date: datetime.date, - end_date: datetime.date - ) -> list[Event]: - weekday_to_event_templates: dict[int: list[dict]] = {} - - for json_weekday_event_templates in json_operating_hours: - weekdays = json_weekday_event_templates["weekday"] - event_templates = json_weekday_event_templates["events"] - - if "-" in weekdays: - start_weekday, end_weekday = weekdays.split("-") - weekdays = range( - ExternalEateries.WEEKDAYS.index(start_weekday), - ExternalEateries.WEEKDAYS.index(end_weekday) + 1 - ) - - else: - weekdays = [ExternalEateries.WEEKDAYS.index(weekdays)] - - for weekday in weekdays: - for event_template in event_templates: - if weekday not in weekday_to_event_templates: - weekday_to_event_templates[weekday] = [] - - weekday_to_event_templates[weekday].append(event_template) - resolved_events: list[Event] = [] - current = start_date - while current <= end_date: - if current.weekday() in weekday_to_event_templates: - for event_template in weekday_to_event_templates[current.weekday()]: - event = Event( - description=event_template["descr"], - canonical_date=current, - menu=ExternalEateries.eatery_menu_from_json(json_dining_items), - start_timestamp=ExternalEateries.timestamp_combined( - current, - ExternalEateries.time_since_midnight(event_template["start"]) - ), - end_timestamp=ExternalEateries.timestamp_combined( - current, - ExternalEateries.time_since_midnight(event_template["end"]) - ) - ) - - resolved_events.append(event) - - current = current + datetime.timedelta(days=1) - - return resolved_events - - @staticmethod - def time_since_midnight(time_str: str) -> datetime.time: - # time_str is like 10:00am or 3:00pm - match = re.fullmatch(r'([0-9]?[0-9]):([0-9][0-9])([ap]m)', time_str) - if not match: - return datetime.time() - - hours = int(match.group(1)) % 12 - minutes = int(match.group(2)) - is_pm = match.group(3) == "pm" - return datetime.time( - hour=hours + (12 if is_pm else 0), - minute=minutes - ) - - @staticmethod - def eatery_menu_from_json(json_dining_items: list) -> Menu: - category_map = {} - for item in json_dining_items: - if item['category'] not in category_map: - category_map[item['category']] = [] - category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) - categories = [] - for category_name in category_map: - categories.append(MenuCategory(category_name, category_map[category_name])) - return Menu(categories=categories) - - @staticmethod - def timestamp_combined(date: datetime.date, time: datetime.time): - """ - Returns the Unix (UTC) timestamp of the combined (date, time) in the - New York timezone. - """ - - tz = pytz.timezone('America/New_York') - return int(tz.localize(datetime.datetime.combine(date, time)).timestamp()) - - def description(self): - return "ExternalEateries" diff --git a/api/dfg/schedule/ClosedSchedule.py b/api/dfg/schedule/ClosedSchedule.py index f53de12..cb60b82 100644 --- a/api/dfg/schedule/ClosedSchedule.py +++ b/api/dfg/schedule/ClosedSchedule.py @@ -1,6 +1,6 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID -from eateries.models import ClosedEventSchedule +# from eateries.models import ClosedEventSchedule class ClosedSchedule(DfgNode): @@ -9,7 +9,7 @@ def __init__(self, eatery_id: EateryID, child: DfgNode): self.child = child def __call__(self, *args, **kwargs) -> list[Eatery]: - ClosedEventSchedule.objects.all() + # ClosedEventSchedule.objects.all() return self.child(*args, **kwargs) def children(self): diff --git a/api/dfg/schedule/DateSchedule.py b/api/dfg/schedule/DateSchedule.py index 5033a0d..6595b85 100644 --- a/api/dfg/schedule/DateSchedule.py +++ b/api/dfg/schedule/DateSchedule.py @@ -1,6 +1,6 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID -from eateries.models import DateEventSchedule +# from eateries.models import DateEventSchedule class DateSchedule(DfgNode): @@ -8,7 +8,7 @@ def __init__(self, eatery_id: EateryID): self.eatery_id = eatery_id def __call__(self, *args, **kwargs) -> list[Eatery]: - DateEventSchedule.objects.all() + # DateEventSchedule.objects.all() return [] def description(self): diff --git a/api/dfg/schedule/DayOfWeekSchedule.py b/api/dfg/schedule/DayOfWeekSchedule.py index 7510b87..829572c 100644 --- a/api/dfg/schedule/DayOfWeekSchedule.py +++ b/api/dfg/schedule/DayOfWeekSchedule.py @@ -1,7 +1,7 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID -from eateries.models import DayOfWeekEventSchedule +# from eateries.models import DayOfWeekEventSchedule class DayOfWeekSchedule(DfgNode): @@ -9,7 +9,7 @@ def __init__(self, eatery_id: EateryID): self.eatery_id = eatery_id def __call__(self, *args, **kwargs) -> list[Eatery]: - DayOfWeekEventSchedule.objects.all() + # DayOfWeekEventSchedule.objects.all() return [] def description(self): diff --git a/api/dfg/wait_times/WaitTimes.py b/api/dfg/wait_times/WaitTimes.py index a6bba92..e172e62 100644 --- a/api/dfg/wait_times/WaitTimes.py +++ b/api/dfg/wait_times/WaitTimes.py @@ -8,7 +8,6 @@ from api.datatype.WaitTimesDay import WaitTimesDay from api.dfg.DfgNode import DfgNode from transactions.models import TransactionHistory -from transactions.serializers import TransactionHistorySerializer class WaitTimes(DfgNode): @@ -30,13 +29,13 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: .values("eatery_id", "block_end_time") \ .annotate(transaction_avg=Avg("transaction_count")) for unit in transaction_avg_counts: - transactions[date].append(TransactionHistorySerializer(unit)) + transactions[date].append(unit) date += timedelta(days=1) self.cache["transactions"] = transactions eatery_wait_times = [] for date in self.cache["transactions"]: - eatery_transaction_avgs = [transaction_avg for transaction_avg in self.cache["transactions"][date] if transaction_avg.data["eatery_id"] == self.eatery_id.value] + eatery_transaction_avgs = [transaction_avg for transaction_avg in self.cache["transactions"][date] if transaction_avg["eatery_id"] == self.eatery_id.value] eatery_wait_times.append(WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs)) return eatery_wait_times @@ -75,29 +74,28 @@ def base_time_to_get_food(eatery_id: EateryID) -> list[int]: def generate_eatery_wait_times_by_day( eatery_id: EateryID, date: date, - transactions: list[TransactionHistorySerializer] + transactions: list ) -> WaitTimesDay: - wait_times_data = [] customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] for index in reversed(range(0, len(transactions))): base_times = WaitTimes.base_time_to_get_food(eatery_id) line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago - how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index].data["transaction_avg"] + how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index]["transaction_avg"] prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) if prev_bucket_guest_arrival > 9: # TODO: Send a slack error here instead print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") else: - customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index].data["transaction_avg"] + customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index]["transaction_avg"] num_customers = customers_waiting_in_line.pop(0) wait_time_low = int(base_times[0] + line_decrease_times[0] * num_customers) wait_time_expected = int(base_times[1] + line_decrease_times[1] * num_customers) wait_time_high = int(base_times[2] + line_decrease_times[2] * num_customers) customers_waiting_in_line.append(0.0) - block_end_time = datetime.strptime(transactions[index].data['block_end_time'], '%H:%M:%S').time() + block_end_time = transactions[index]['block_end_time'] timestamp = int(WaitTimes.timestamp_combined(date, block_end_time) - 5 * 60 / 2) wait_times_data.insert(0, WaitTime( timestamp=timestamp, diff --git a/api/models.py b/api/models.py deleted file mode 100644 index d49766e..0000000 --- a/api/models.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.db import models - -# Create your models here. \ No newline at end of file diff --git a/api/views.py b/api/views.py index 7a68d8f..c6f32ea 100644 --- a/api/views.py +++ b/api/views.py @@ -6,7 +6,7 @@ from api.dfg.CornellDiningNow import CornellDiningNow from api.dfg.EateryStubs import EateryStubs -from api.dfg.ExternalEateries import ExternalEateries +from api.dfg.EateriesFromDB import EateriesFromDB from api.dfg.macros.EateryEvents import EateryEvents from api.dfg.util.DictResponseWrapper import DictResponseWrapper @@ -41,7 +41,7 @@ ) ), LeftMergeEateries( - ExternalEateries(), + EateriesFromDB(), LeftMergeEateries( CornellDiningNow(), EateryStubs() diff --git a/eateries/admin.py b/eateries/admin.py index 128f8c5..7f65f66 100644 --- a/eateries/admin.py +++ b/eateries/admin.py @@ -1,16 +1,15 @@ from django.contrib import admin -from eateries.models import EateryStore, ItemStore, SubItemStore, CategoryStore, MenuStore, ExceptionStore, CategoryItemAssociation, EventSchedule, DayOfWeekEventSchedule, DateEventSchedule, ClosedEventSchedule +import eateries.models as models # Register your models here. -admin.site.register(EateryStore) -admin.site.register(MenuStore) -admin.site.register(ItemStore) -admin.site.register(SubItemStore) -admin.site.register(ExceptionStore) -admin.site.register(CategoryStore) -admin.site.register(CategoryItemAssociation) -admin.site.register(EventSchedule) -admin.site.register(DayOfWeekEventSchedule) -admin.site.register(DateEventSchedule) -admin.site.register(ClosedEventSchedule) \ No newline at end of file +admin.site.register(models.EateryStore) +admin.site.register(models.MenuStore) +admin.site.register(models.ItemStore) +admin.site.register(models.SubItemStore) +admin.site.register(models.ExceptionStore) +admin.site.register(models.CategoryStore) +admin.site.register(models.CategoryItemAssociation) +admin.site.register(models.DayOfWeekEventSchedule) +admin.site.register(models.DateEventSchedule) +admin.site.register(models.ClosedEventSchedule) \ No newline at end of file diff --git a/eateries/management/commands/create_db_snapshot.py b/eateries/management/commands/create_db_snapshot.py new file mode 100644 index 0000000..538b266 --- /dev/null +++ b/eateries/management/commands/create_db_snapshot.py @@ -0,0 +1,60 @@ + +from django.core.management.base import BaseCommand + +from datetime import datetime +from pathlib import Path + +from eateries.util import SnapshotFileName + +import eateries.models as models +import eateries.serializers as serializers +import pytz +import json + +class Command(BaseCommand): + help = 'Saves the current state of the database' + + def write_to_file(self, serialized_lst, file_path): + with open(file_path, "w") as file: + for obj in serialized_lst.data: + file.write(json.dumps(obj) + "\n") + + def handle(self, *args, **options): + tzinfo = pytz.timezone("US/Eastern") + time = datetime.now(tzinfo).strftime("%Y-%m-%d %H:%M:%S") + folder_path = f"db_snapshots/{time}" + Path(folder_path).mkdir(parents=True, exist_ok=True) + + eateries = models.EateryStore.objects.all() + self.write_to_file(serializers.EateryStoreSerializer(eateries, many=True), f"{folder_path}/{SnapshotFileName.EATERY_STORE.value}") + + exceptions = models.ExceptionStore.objects.filter(end_timestamp__gte=datetime.now().timestamp()) + self.write_to_file(serializers.ExceptionStoreSerializer(exceptions, many=True), f"{folder_path}/{SnapshotFileName.EXCEPTION_STORE.value}") + + menus = models.MenuStore.objects.all() + self.write_to_file(serializers.MenuStoreSerializer(menus, many=True), f"{folder_path}/{SnapshotFileName.MENU_STORE.value}") + + categories = models.CategoryStore.objects.all() + self.write_to_file(serializers.CategoryStoreSerializer(categories, many=True), f"{folder_path}/{SnapshotFileName.CATEGORY_STORE.value}") + + items = models.ItemStore.objects.all() + self.write_to_file(serializers.ItemStoreSerializer(items, many=True), f"{folder_path}/{SnapshotFileName.ITEM_STORE.value}") + + subitems = models.SubItemStore.objects.all() + self.write_to_file(serializers.SubItemStoreSerializer(subitems, many=True), f"{folder_path}/{SnapshotFileName.SUBITEM_STORE.value}") + + category_item_associations = models.CategoryItemAssociation.objects.all() + self.write_to_file(serializers.CategoryItemAssociationSerializer(category_item_associations, many=True), f"{folder_path}/{SnapshotFileName.CATEGORY_ITEM_ASSOCIATION.value}") + + # date_event_schedules = models.DateEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) + # self.write_to_file(date_event_schedules, f"{folder_path}/{SnapshotFileName.DATE_EVENT_SCHEDULE}") + + # closed_event_schedules = models.ClosedEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) + # self.write_to_file(closed_event_schedules, f"{folder_path}/{SnapshotFileName.CLOSED_EVENT_SCHEDULE}") + + # day_of_week_event_schedules = models.DayOfWeekEventSchedule.objects.all() + # self.write_to_file(day_of_week_event_schedules, f"{folder_path}/{SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE}") + + # # TODO: Need to filter here for only the valid schedules + # event_schedules = models.EventSchedule.objects.all() + # self.write_to_file(event_schedules, f"{folder_path}/{SnapshotFileName.EVENT_SCHEDULE}") \ No newline at end of file diff --git a/eateries/management/commands/ingest_db_snapshot.py b/eateries/management/commands/ingest_db_snapshot.py new file mode 100644 index 0000000..f206414 --- /dev/null +++ b/eateries/management/commands/ingest_db_snapshot.py @@ -0,0 +1,42 @@ + +from django.core.management.base import BaseCommand + +from eateries.util import SnapshotFileName + +import eateries.serializers as serializers + +import json + +class Command(BaseCommand): + help = 'Overrides current state of the db with a db snapshot' + + # Only writes data if the table has been flushed + def ingest_data(self, serializer, file_name: SnapshotFileName): + folder_path = "db_snapshots/2022-01-10 13:05:44" + with open(f"{folder_path}/{file_name.value}", "r") as file: + json_objs = [] + for line in file: + if (len(line) > 2): + json_objs.append(json.loads(line)) + serialized_objs = serializer(data=json_objs, many=True) + serialized_objs.is_valid() + serialized_objs.save() + + def handle(self, *args, **options): + self.ingest_data(serializers.EateryStoreSerializer, SnapshotFileName.EATERY_STORE) + self.ingest_data(serializers.ExceptionStoreSerializer, SnapshotFileName.EXCEPTION_STORE) + self.ingest_data(serializers.MenuStoreSerializer, SnapshotFileName.MENU_STORE) + self.ingest_data(serializers.CategoryStoreSerializer, SnapshotFileName.CATEGORY_STORE) + self.ingest_data(serializers.ItemStoreSerializer, SnapshotFileName.ITEM_STORE) + self.ingest_data(serializers.SubItemStoreSerializer, SnapshotFileName.SUBITEM_STORE) + self.ingest_data(serializers.CategoryItemAssociationSerializer, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) + + + # with open(f"{folder_path}/{SnapshotFileName.EXCEPTION_STORE.value}", "r") as file: + # serialized_objs = [] + # for line in file: + # if (len(line) > 2): + # serialized_objs.append(json.loads(line)) + # exception_objs = serializers.ExceptionStoreSerializer(data=serialized_objs, many=True) + # exception_objs.is_valid() + # eatery_objs.save() \ No newline at end of file diff --git a/eateries/migrations/0001_initial.py b/eateries/migrations/0001_initial.py index 8153937..a571daa 100644 --- a/eateries/migrations/0001_initial.py +++ b/eateries/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2022-01-07 18:50 +# Generated by Django 4.0 on 2022-01-10 17:56 from django.db import migrations, models import django.db.models.deletion @@ -20,7 +20,7 @@ class Migration(migrations.Migration): ('menu_summary', models.CharField(blank=True, max_length=60)), ('image_url', models.URLField(blank=True)), ('location', models.CharField(blank=True, max_length=30)), - ('campus_area', models.CharField(blank=True, choices=[('WST', 'West'), ('NRTH', 'North'), ('CNTRL', 'Central'), ('CTWN', 'Collegetown'), ('NN', '')], default='NN', max_length=5)), + ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), ('online_order', models.BooleanField(blank=True, null=True)), ('online_order_url', models.URLField(blank=True)), ('latitude', models.FloatField(blank=True, null=True)), @@ -30,12 +30,6 @@ class Migration(migrations.Migration): ('payment_accepts_cash', models.BooleanField(blank=True, null=True)), ], ), - migrations.CreateModel( - name='EventSchedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), migrations.CreateModel( name='ItemStore', fields=[ @@ -43,36 +37,8 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=40)), ('description', models.CharField(blank=True, max_length=200)), ('base_price', models.FloatField(blank=True, null=True)), - ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - ), - migrations.CreateModel( - name='ClosedEventSchedule', - fields=[ - ('eventschedule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='eateries.eventschedule')), - ('canonical_date', models.DateField()), - ], - bases=('eateries.eventschedule',), - ), - migrations.CreateModel( - name='DateEventSchedule', - fields=[ - ('eventschedule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='eateries.eventschedule')), - ('canonical_date', models.DateField()), - ('start_timestamp', models.DateTimeField()), - ('end_timestamp', models.DateTimeField()), - ], - bases=('eateries.eventschedule',), - ), - migrations.CreateModel( - name='DayOfWeekEventSchedule', - fields=[ - ('eventschedule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='eateries.eventschedule')), - ('day_of_week', models.CharField(choices=[('MON', 'Monday'), ('TUE', 'Tuesday'), ('WED', 'Wednesday'), ('THU', 'Thursday'), ('FRI', 'Friday'), ('SAT', 'Saturday'), ('SUN', 'Sunday')], max_length=3)), - ('start', models.TimeField()), - ('end', models.TimeField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), ], - bases=('eateries.eventschedule',), ), migrations.CreateModel( name='SubItemStore', @@ -82,7 +48,7 @@ class Migration(migrations.Migration): ('total_price', models.FloatField(blank=True, null=True)), ('name', models.CharField(max_length=40)), ('item_subsection', models.CharField(max_length=40)), - ('item_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), + ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), ], ), migrations.CreateModel( @@ -90,7 +56,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=40)), - ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), ], ), migrations.CreateModel( @@ -98,9 +64,9 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('description', models.CharField(max_length=250)), - ('start_timestamp', models.DateTimeField()), - ('end_timestamp', models.DateTimeField()), - ('eatery_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('start_timestamp', models.IntegerField()), + ('end_timestamp', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), ], ), migrations.CreateModel( @@ -108,15 +74,15 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('category', models.CharField(max_length=40)), - ('menu_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), ], ), migrations.CreateModel( name='CategoryItemAssociation', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('category_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.categorystore')), - ('item_id', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.categorystore')), + ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), ], ), ] diff --git a/eateries/migrations/0002_closedeventschedule_and_more.py b/eateries/migrations/0002_closedeventschedule_and_more.py new file mode 100644 index 0000000..358bf34 --- /dev/null +++ b/eateries/migrations/0002_closedeventschedule_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.0 on 2022-01-10 18:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ClosedEventSchedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('canonical_date', models.DateField()), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterUniqueTogether( + name='categoryitemassociation', + unique_together={('item', 'category')}, + ), + ] diff --git a/eateries/migrations/0003_closedeventschedule_eatery_and_more.py b/eateries/migrations/0003_closedeventschedule_eatery_and_more.py new file mode 100644 index 0000000..b09825b --- /dev/null +++ b/eateries/migrations/0003_closedeventschedule_eatery_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 4.0 on 2022-01-10 18:19 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0002_closedeventschedule_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='closedeventschedule', + name='eatery', + field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore'), + preserve_default=False, + ), + migrations.AddField( + model_name='closedeventschedule', + name='event_description', + field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), + preserve_default=False, + ), + ] diff --git a/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py b/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py new file mode 100644 index 0000000..de796ef --- /dev/null +++ b/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py @@ -0,0 +1,40 @@ +# Generated by Django 4.0 on 2022-01-10 18:23 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0003_closedeventschedule_eatery_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='DayOfWeekEventSchedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('day_of_week', models.CharField(choices=[('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday')], max_length=10)), + ('start', models.TimeField()), + ('end', models.TimeField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DateEventSchedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('canonical_date', models.DateField()), + ('start_timestamp', models.IntegerField()), + ('end_timestamp', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/eateries/migrations/0005_dateeventschedule_event_description_and_more.py b/eateries/migrations/0005_dateeventschedule_event_description_and_more.py new file mode 100644 index 0000000..e6a89a3 --- /dev/null +++ b/eateries/migrations/0005_dateeventschedule_event_description_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.0 on 2022-01-10 18:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0004_dayofweekeventschedule_dateeventschedule'), + ] + + operations = [ + migrations.AddField( + model_name='dateeventschedule', + name='event_description', + field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), + preserve_default=False, + ), + migrations.AddField( + model_name='dateeventschedule', + name='menu', + field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore'), + preserve_default=False, + ), + migrations.AddField( + model_name='dayofweekeventschedule', + name='event_description', + field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), + preserve_default=False, + ), + migrations.AddField( + model_name='dayofweekeventschedule', + name='menu', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore'), + preserve_default=False, + ), + ] diff --git a/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py b/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py new file mode 100644 index 0000000..0474536 --- /dev/null +++ b/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py @@ -0,0 +1,70 @@ +# Generated by Django 4.0 on 2022-01-10 18:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0005_dateeventschedule_event_description_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='categoryitemassociation', + unique_together=set(), + ), + migrations.AlterField( + model_name='categoryitemassociation', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='categorystore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='closedeventschedule', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='dateeventschedule', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='dayofweekeventschedule', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='exceptionstore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='itemstore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='menustore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='subitemstore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterUniqueTogether( + name='categorystore', + unique_together={('menu', 'category')}, + ), + migrations.AlterUniqueTogether( + name='menustore', + unique_together={('eatery', 'name')}, + ), + ] diff --git a/eateries/models.py b/eateries/models.py index 725789f..cf97678 100644 --- a/eateries/models.py +++ b/eateries/models.py @@ -2,18 +2,18 @@ from datetime import datetime class EateryStore(models.Model): class CampusArea(models.TextChoices): - WEST = 'WST', 'West' - NORTH = 'NRTH', 'North' - CENTRAL = 'CNTRL', 'Central' - COLLEGETOWN = 'CTWN', 'Collegetown' - NONE = 'NN', '' + WEST = 'West' + NORTH = 'North' + CENTRAL = 'Central' + COLLEGETOWN = 'Collegetown' + NONE = '' id = models.IntegerField(primary_key=True) name = models.CharField(max_length=40, blank=True) menu_summary = models.CharField(max_length = 60, blank=True) image_url = models.URLField(blank=True) location = models.CharField(max_length=30, blank=True) - campus_area = models.CharField(max_length=5, choices=CampusArea.choices, default=CampusArea.NONE, blank=True) + campus_area = models.CharField(max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True) online_order = models.BooleanField(null = True, blank=True) online_order_url = models.URLField(blank=True) latitude = models.FloatField(null = True, blank=True) @@ -23,73 +23,86 @@ class CampusArea(models.TextChoices): payment_accepts_cash = models.BooleanField(null = True, blank=True) class ExceptionStore(models.Model): - eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) description = models.CharField(max_length = 250) - start_timestamp = models.DateTimeField() - end_timestamp = models.DateTimeField() + start_timestamp = models.IntegerField() + end_timestamp = models.IntegerField() class MenuStore(models.Model): - eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) name = models.CharField(max_length = 40) + class Meta: + unique_together = ('eatery', 'name') + class CategoryStore(models.Model): - menu_id = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + id = models.IntegerField(primary_key=True) + menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) category = models.CharField(max_length = 40) + class Meta: + unique_together = ('menu', 'category') + class ItemStore(models.Model): - eatery_id = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) name = models.CharField(max_length=40) description = models.CharField(max_length = 200, blank=True) base_price = models.FloatField(null = True, blank=True) class SubItemStore(models.Model): - item_id = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) + id = models.IntegerField(primary_key=True) + item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) additional_price = models.FloatField(null = True, blank=True) total_price = models.FloatField(null = True, blank=True) name = models.CharField(max_length=40) item_subsection = models.CharField(max_length=40) class CategoryItemAssociation(models.Model): - item_id = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) - category_id = models.ForeignKey(CategoryStore, on_delete=models.DO_NOTHING) - - + id = models.IntegerField(primary_key=True) + item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) + category = models.ForeignKey(CategoryStore, on_delete=models.DO_NOTHING) class EventDescription(models.TextChoices): - BREAKFAST = 'BRKFST' - BRUNCH = 'BRNCH' - LUNCH = 'LNCH' - DINNER = 'DNNR' - GENERAL = 'GNRL' + BREAKFAST = 'Breakfast' + BRUNCH = 'Brunch' + LUNCH = 'Lunch' + DINNER = 'Dinner' + GENERAL = 'General' class EventSchedule(models.Model): - eatery_id: models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + class Meta: + abstract=True class DayOfWeekEventSchedule(EventSchedule): class DayOfTheWeek(models.TextChoices): - MONDAY = 'MON' - TUESDAY = 'TUE' - WEDNESDAY = 'WED' - THURSDAY = 'THU' - FRIDAY = 'FRI' - SATURDAY = 'SAT' - SUNDAY = 'SUN' - - event_description: models.CharField(choices=EventDescription.choices, max_length = 10) - menu: models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=3) + MONDAY = 'Monday' + TUESDAY = 'Tuesday' + WEDNESDAY = 'Wednesday' + THURSDAY = 'Thursday' + FRIDAY = 'Friday' + SATURDAY = 'Saturday' + SUNDAY = 'Sunday' + + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + menu= models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=10) start = models.TimeField() end = models.TimeField() class DateEventSchedule(EventSchedule): - event_description: models.CharField(choices=EventDescription.choices, max_length = 10) - menu: models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) canonical_date = models.DateField() - start_timestamp = models.DateTimeField() - end_timestamp = models.DateTimeField() + start_timestamp = models.IntegerField() + end_timestamp = models.IntegerField() class ClosedEventSchedule(EventSchedule): - event_description: models.CharField(choices=EventDescription.choices, max_length = 10) + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) canonical_date = models.DateField() diff --git a/eateries/scripts/create_db_snapshot.py b/eateries/scripts/create_db_snapshot.py deleted file mode 100644 index a5e7f40..0000000 --- a/eateries/scripts/create_db_snapshot.py +++ /dev/null @@ -1,9 +0,0 @@ - -from eateries.models import EateryStore - - -def create_snapshot(): - eateries = EateryStore.objects.all() - - -create_snapshot() \ No newline at end of file diff --git a/eateries/scripts/ingest_db_snpashot.py b/eateries/scripts/ingest_db_snpashot.py deleted file mode 100644 index e69de29..0000000 diff --git a/eateries/serializers.py b/eateries/serializers.py new file mode 100644 index 0000000..c05f1ea --- /dev/null +++ b/eateries/serializers.py @@ -0,0 +1,38 @@ +from rest_framework import serializers + +import eateries.models as models + +class EateryStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.EateryStore + fields = '__all__' + +class ExceptionStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.ExceptionStore + fields = '__all__' + +class MenuStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.MenuStore + fields = '__all__' + +class ItemStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.ItemStore + fields = '__all__' + +class SubItemStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.SubItemStore + fields = '__all__' + +class CategoryStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.CategoryStore + fields = '__all__' + +class CategoryItemAssociationSerializer(serializers.ModelSerializer): + class Meta: + model = models.CategoryItemAssociation + fields = '__all__' \ No newline at end of file diff --git a/eateries/util.py b/eateries/util.py new file mode 100644 index 0000000..e24c9ea --- /dev/null +++ b/eateries/util.py @@ -0,0 +1,17 @@ +from enum import Enum + +class SnapshotFileName(Enum): + EATERY_STORE = "eatery_store.txt" + EXCEPTION_STORE = "exception_store.txt" + CATEGORY_STORE = "category_store.txt" + MENU_STORE = "menu_store.txt" + ITEM_STORE = "item_store.txt" + SUBITEM_STORE = "subitem_store.txt" + CATEGORY_ITEM_ASSOCIATION = "category_item_association.txt" + EVENT_SCHEDULE = "event_schedule.txt" + DAY_OF_WEEK_EVENT_SCHEDULE = "day_of_week_event_schedule.txt" + DATE_EVENT_SCHEDULE = "date_event_schedule.txt" + CLOSED_EVENT_SCHEDULE = "closed_event_schedule.txt" + TRANSACTION_HISTORY = "transaction_history.txt" + + \ No newline at end of file diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index 9b5924c..ece4a45 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -87,11 +87,7 @@ def __init__(self, data): def process(self): if self._data["TIMESTAMP"] == "Invalid date": - return { - "success": False, - "result": None, - "error": "Invalid date" - } + return 0 tz = pytz.timezone('America/New_York') recent_datetime = tz.localize(datetime.strptime(self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p')) canonical_date = recent_datetime.date() @@ -109,14 +105,8 @@ def process(self): num_inserted += 1 try: TransactionHistory.objects.create(eatery_id = internal_id, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) - except: + except Exception as e: + print(e) num_inserted -= 1 - return { - "success": True, - "result": { - "num_inserted": num_inserted, - "ignored_names": list(ignored_names) - }, - "error": None - } + return num_inserted diff --git a/transactions/management/commands/ingest_log_transactions.py b/transactions/management/commands/ingest_log_transactions.py index 6d14b93..82e713e 100644 --- a/transactions/management/commands/ingest_log_transactions.py +++ b/transactions/management/commands/ingest_log_transactions.py @@ -6,7 +6,6 @@ from transactions.controllers.update_transactions_controller import UpdateTransactionsController from transactions.models import TransactionHistory - class Command(BaseCommand): help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistory table' @@ -23,9 +22,8 @@ def handle(self, *args, **options): print(timestamp) if timestamp.year == 2021 and timestamp.month > 7: counter += 1 - res = UpdateTransactionsController(data).process() - if res["success"]: - num_inserted += res["result"]["num_inserted"] + inserted = UpdateTransactionsController(data).process() + num_inserted += inserted except Exception as e: pass print("{} Entries Deleted".format(num_deleted)) diff --git a/transactions/migrations/0001_initial.py b/transactions/migrations/0001_initial.py index 5835155..5178f47 100644 --- a/transactions/migrations/0001_initial.py +++ b/transactions/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 4.0 on 2021-12-26 15:40 +# Generated by Django 4.0 on 2022-01-10 17:56 from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -8,6 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('eateries', '0001_initial'), ] operations = [ @@ -15,11 +17,18 @@ class Migration(migrations.Migration): name='TransactionHistory', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), ('canonical_date', models.DateField()), - ('start_timestamp', models.TimeField()), - ('end_timestamp', models.TimeField()), + ('block_end_time', models.TimeField()), ('transaction_count', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), ], ), + migrations.AddIndex( + model_name='transactionhistory', + index=models.Index(fields=['canonical_date'], name='transaction_canonic_a40422_idx'), + ), + migrations.AlterUniqueTogether( + name='transactionhistory', + unique_together={('eatery_id', 'block_end_time', 'canonical_date')}, + ), ] diff --git a/transactions/migrations/0002_rename_end_timestamp_transactionhistory_timestamp_and_more.py b/transactions/migrations/0002_rename_end_timestamp_transactionhistory_timestamp_and_more.py deleted file mode 100644 index 30b0ac5..0000000 --- a/transactions/migrations/0002_rename_end_timestamp_transactionhistory_timestamp_and_more.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 4.0 on 2021-12-26 18:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('transactions', '0001_initial'), - ] - - operations = [ - migrations.RenameField( - model_name='transactionhistory', - old_name='end_timestamp', - new_name='timestamp', - ), - migrations.AlterUniqueTogether( - name='transactionhistory', - unique_together={('name', 'timestamp', 'canonical_date')}, - ), - migrations.AddIndex( - model_name='transactionhistory', - index=models.Index(fields=['name', 'timestamp', 'canonical_date'], name='transaction_name_204126_idx'), - ), - migrations.RemoveField( - model_name='transactionhistory', - name='start_timestamp', - ), - ] diff --git a/transactions/migrations/0003_remove_transactionhistory_transaction_name_204126_idx_and_more.py b/transactions/migrations/0003_remove_transactionhistory_transaction_name_204126_idx_and_more.py deleted file mode 100644 index 183afed..0000000 --- a/transactions/migrations/0003_remove_transactionhistory_transaction_name_204126_idx_and_more.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.0 on 2021-12-27 15:40 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('transactions', '0002_rename_end_timestamp_transactionhistory_timestamp_and_more'), - ] - - operations = [ - migrations.RemoveIndex( - model_name='transactionhistory', - name='transaction_name_204126_idx', - ), - migrations.AddIndex( - model_name='transactionhistory', - index=models.Index(fields=['canonical_date'], name='transaction_canonic_a40422_idx'), - ), - ] diff --git a/transactions/migrations/0004_rename_timestamp_transactionhistory_block_end_time_and_more.py b/transactions/migrations/0004_rename_timestamp_transactionhistory_block_end_time_and_more.py deleted file mode 100644 index 340380c..0000000 --- a/transactions/migrations/0004_rename_timestamp_transactionhistory_block_end_time_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.0 on 2021-12-28 19:52 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('transactions', '0003_remove_transactionhistory_transaction_name_204126_idx_and_more'), - ] - - operations = [ - migrations.RenameField( - model_name='transactionhistory', - old_name='timestamp', - new_name='block_end_time', - ), - migrations.AlterUniqueTogether( - name='transactionhistory', - unique_together={('name', 'block_end_time', 'canonical_date')}, - ), - ] diff --git a/transactions/migrations/0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more.py b/transactions/migrations/0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more.py deleted file mode 100644 index 11ad3cc..0000000 --- a/transactions/migrations/0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.0 on 2021-12-28 20:18 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('transactions', '0004_rename_timestamp_transactionhistory_block_end_time_and_more'), - ] - - operations = [ - migrations.RenameField( - model_name='transactionhistory', - old_name='block_end_time', - new_name='bucket_end_time', - ), - migrations.AlterUniqueTogether( - name='transactionhistory', - unique_together={('name', 'bucket_end_time', 'canonical_date')}, - ), - ] diff --git a/transactions/migrations/0006_alter_transactionhistory_bucket_end_time.py b/transactions/migrations/0006_alter_transactionhistory_bucket_end_time.py deleted file mode 100644 index d8dfe3e..0000000 --- a/transactions/migrations/0006_alter_transactionhistory_bucket_end_time.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0 on 2021-12-28 20:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('transactions', '0005_rename_block_end_time_transactionhistory_bucket_end_time_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='transactionhistory', - name='bucket_end_time', - field=models.IntegerField(), - ), - ] diff --git a/transactions/migrations/0007_alter_transactionhistory_bucket_end_time.py b/transactions/migrations/0007_alter_transactionhistory_bucket_end_time.py deleted file mode 100644 index 9d28078..0000000 --- a/transactions/migrations/0007_alter_transactionhistory_bucket_end_time.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0 on 2021-12-28 20:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('transactions', '0006_alter_transactionhistory_bucket_end_time'), - ] - - operations = [ - migrations.AlterField( - model_name='transactionhistory', - name='bucket_end_time', - field=models.TimeField(), - ), - ] diff --git a/transactions/migrations/0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more.py b/transactions/migrations/0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more.py deleted file mode 100644 index fcba274..0000000 --- a/transactions/migrations/0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.0 on 2021-12-28 20:31 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('transactions', '0007_alter_transactionhistory_bucket_end_time'), - ] - - operations = [ - migrations.RenameField( - model_name='transactionhistory', - old_name='bucket_end_time', - new_name='block_end_time', - ), - migrations.AlterUniqueTogether( - name='transactionhistory', - unique_together={('name', 'block_end_time', 'canonical_date')}, - ), - ] diff --git a/transactions/migrations/0009_alter_transactionhistory_unique_together_and_more.py b/transactions/migrations/0009_alter_transactionhistory_unique_together_and_more.py deleted file mode 100644 index 98d905a..0000000 --- a/transactions/migrations/0009_alter_transactionhistory_unique_together_and_more.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 4.0 on 2022-01-03 22:00 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('transactions', '0008_rename_bucket_end_time_transactionhistory_block_end_time_and_more'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='transactionhistory', - unique_together=set(), - ), - migrations.AddField( - model_name='transactionhistory', - name='eatery_id', - field=models.IntegerField(default=3), - preserve_default=False, - ), - migrations.AlterUniqueTogether( - name='transactionhistory', - unique_together={('eatery_id', 'block_end_time', 'canonical_date')}, - ), - migrations.RemoveField( - model_name='transactionhistory', - name='name', - ), - ] diff --git a/transactions/models.py b/transactions/models.py index 2b994ab..53d660b 100644 --- a/transactions/models.py +++ b/transactions/models.py @@ -1,5 +1,5 @@ from django.db import models - +from eateries.models import EateryStore # Create your models here. # [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] @@ -7,7 +7,7 @@ class TransactionHistory(models.Model): class Meta: unique_together = ('eatery_id', 'block_end_time', 'canonical_date') indexes = [models.Index(fields = ['canonical_date'])] - eatery_id = models.IntegerField() + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) canonical_date = models.DateField() block_end_time = models.TimeField() transaction_count = models.IntegerField() \ No newline at end of file diff --git a/transactions/serializers.py b/transactions/serializers.py deleted file mode 100644 index 0356459..0000000 --- a/transactions/serializers.py +++ /dev/null @@ -1,25 +0,0 @@ -import json - -from rest_framework import serializers -from .models import TransactionHistory - -class TransactionHistorySerializer(serializers.ModelSerializer): - transaction_avg = serializers.SerializerMethodField("get_transaction_avg") - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def get_transaction_avg(self, obj): - try: - return obj['transaction_avg'] - except: - return None - class Meta: - model = TransactionHistory - fields = ( - "eatery_id", - "canonical_date", - "block_end_time", - "transaction_avg" - ) - read_only_fields = fields \ No newline at end of file From a78521e0ea9d1ad634e8d3a83234684690812336 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 10 Jan 2022 15:56:10 -0500 Subject: [PATCH 037/305] Add dayofweek schedule, items --- api/datatype/Event.py | 23 ++++--- api/datatype/MenuItem.py | 27 ++++++-- api/datatype/MenuItemSection.py | 20 ++++++ api/datatype/MenuSubItem.py | 28 ++++++++ api/dfg/macros/EateryEvents.py | 21 +++--- api/dfg/schedule/CacheMenuInjection.py | 88 ++++++++++++++++++++++++++ api/dfg/schedule/ClosedSchedule.py | 3 +- api/dfg/schedule/DateSchedule.py | 3 +- api/dfg/schedule/DayOfWeekSchedule.py | 31 +++++++-- api/dfg/wait_times/WaitTimes.py | 13 +--- eateries/models.py | 3 + eateries/util.py | 4 +- 12 files changed, 222 insertions(+), 42 deletions(-) create mode 100644 api/datatype/MenuItemSection.py create mode 100644 api/datatype/MenuSubItem.py create mode 100644 api/dfg/schedule/CacheMenuInjection.py diff --git a/api/datatype/Event.py b/api/datatype/Event.py index 97e1975..c0c6dbe 100644 --- a/api/datatype/Event.py +++ b/api/datatype/Event.py @@ -1,7 +1,5 @@ from typing import Optional - -from datetime import date, datetime, time - +from datetime import date, time, datetime from api.datatype.Menu import Menu import pytz @@ -50,9 +48,14 @@ def from_json(event_json): def __contains__(self, item: int): return self.start_timestamp <= item <= self.end_timestamp - -def _combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: - return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) + #TODO: Restructure to move to some util file into major folder outside of api + @staticmethod + def combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: + """ + Returns the Unix (UTC) timestamp of the combined (date, time) in the + New York timezone. + """ + return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): @@ -63,17 +66,17 @@ def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Op return events elif tzinfo is not None and start is not None and end is None: - start_ts = _combined_timestamp(start, time(), tzinfo) + start_ts = Event.combined_timestamp(start, time(), tzinfo) return [event for event in events if ( (start_ts in event) or start == event.canonical_date )] elif tzinfo is not None and start is not None and end is not None: - start_ts = _combined_timestamp(start, time(), tzinfo) - end_ts = _combined_timestamp(end, time(), tzinfo) + start_ts = Event.combined_timestamp(start, time(), tzinfo) + end_ts = Event.combined_timestamp(end, time(), tzinfo) return [event for event in events if ( (start_ts in event) or (end_ts in event) or start <= event.canonical_date <= end )] else: - raise Exception(f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}") + raise Exception(f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}") \ No newline at end of file diff --git a/api/datatype/MenuItem.py b/api/datatype/MenuItem.py index 1111353..b611652 100644 --- a/api/datatype/MenuItem.py +++ b/api/datatype/MenuItem.py @@ -1,3 +1,7 @@ +from typing import Optional + +from api.datatype.MenuItemSection import MenuItemSection + class MenuItem: @staticmethod @@ -9,21 +13,34 @@ def from_cornell_dining_json(json_item: dict): def __init__( self, - healthy: bool, - name: str + name: str, + healthy: Optional[bool] = None, + base_price: Optional[float] = None, + description: Optional[str] = None, + sections: Optional[MenuItemSection] = None ): self.healthy = healthy self.name = name + self.base_price = base_price + self.description = description + self.sections = sections def to_json(self): return { "healthy": self.healthy, - "name": self.name + "name": self.name, + "base_price": self.base_price, + "description": self.description, + "sections": None if self.sections is None else [section.to_json() for section in self.sections] } @staticmethod def from_json(item_json): return MenuItem( + name=item_json["name"], healthy=item_json["healthy"], - name=item_json["name"] - ) + base_price=item_json["base_price"], + description=item_json["description"], + sections=None if "sections" not in item_json or item_json["sections"] is None + else [MenuItemSection.from_json(section) for section in item_json["sections"]] + ) diff --git a/api/datatype/MenuItemSection.py b/api/datatype/MenuItemSection.py new file mode 100644 index 0000000..770c376 --- /dev/null +++ b/api/datatype/MenuItemSection.py @@ -0,0 +1,20 @@ +from api.datatype.MenuSubItem import MenuSubItem + +class MenuItemSection: + + def __init__(self, name: str, subitems: list[MenuSubItem]): + self.name = name + self.subitems = subitems + + def to_json(self): + return { + "name": self.name, + "subitems": [item.to_json() for item in self.subitems] + } + + @staticmethod + def from_json(section_json): + return MenuItemSection( + name=section_json["name"], + subitems=[MenuSubItem.from_json(item) for item in section_json["subitems"]] + ) diff --git a/api/datatype/MenuSubItem.py b/api/datatype/MenuSubItem.py new file mode 100644 index 0000000..9fbfe02 --- /dev/null +++ b/api/datatype/MenuSubItem.py @@ -0,0 +1,28 @@ +from typing import Optional + +class MenuSubItem: + + def __init__( + self, + name: str, + total_price: Optional[float], + additional_price: Optional[float] + ): + self.name = name + self.total_price = total_price + self.additional_price = additional_price + + def to_json(self): + return { + "name": self.name, + "total_price": self.total_price, + "additional_price": self.additional_price + } + + @staticmethod + def from_json(item_json): + return MenuSubItem( + name=item_json["name"], + total_price=None if "total_price" not in item_json else item_json["total_price"], + additional_price=None if "additional_price" not in item_json else item_json["additional_price"] + ) diff --git a/api/dfg/macros/EateryEvents.py b/api/dfg/macros/EateryEvents.py index 204e5e7..26c7079 100644 --- a/api/dfg/macros/EateryEvents.py +++ b/api/dfg/macros/EateryEvents.py @@ -8,20 +8,25 @@ from api.dfg.macros.LeftMergeEvents import LeftMergeEvents from api.datatype.Eatery import EateryID +from api.dfg.schedule.CacheMenuInjection import CacheMenuInjection # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if # conflict) class EateryEvents(DfgNode): def __init__(self, eatery_id: EateryID, cache): - self.macro = ClosedSchedule( - eatery_id, - LeftMergeEvents( - DateSchedule(eatery_id), + self.macro = CacheMenuInjection( + ClosedSchedule( + eatery_id, LeftMergeEvents( - DayOfWeekSchedule(eatery_id), - CornellDiningEvents(eatery_id, cache) - ) - ) + DateSchedule(eatery_id, cache), + LeftMergeEvents( + DayOfWeekSchedule(eatery_id, cache), + CornellDiningEvents(eatery_id, cache) + ) + ), + cache + ), + cache ) def children(self): diff --git a/api/dfg/schedule/CacheMenuInjection.py b/api/dfg/schedule/CacheMenuInjection.py new file mode 100644 index 0000000..726646e --- /dev/null +++ b/api/dfg/schedule/CacheMenuInjection.py @@ -0,0 +1,88 @@ +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem +from api.datatype.MenuItemSection import MenuItemSection +from api.datatype.MenuSubItem import MenuSubItem +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from eateries.models import MenuStore, CategoryItemAssociation, SubItemStore +# from eateries.models import DateEventSchedule + +class CacheMenuInjection(DfgNode): + + def __init__(self, child: DfgNode, cache): + self.cache = cache + self.child = child + + def __call__(self, *args, **kwargs) -> list[Eatery]: + if "menus" not in self.cache: + eatery_menus_categories_map = {} + associations = CategoryItemAssociation.objects \ + .select_related("item") \ + .select_related("category") \ + .select_related("category__menu") \ + .all() + + subitems = SubItemStore.objects.all() + item_subitem_map = {} + for subitem in subitems: + item_id = subitem.item_id + item_subsection = subitem.item_subsection + if item_id not in item_subitem_map: + item_subitem_map[item_id] = {} + if item_subsection not in item_subitem_map[item_id]: + item_subitem_map[item_id][item_subsection] = [] + item_subitem_map[item_id][item_subsection].append( + MenuSubItem( + name=subitem.name, + additional_price=subitem.additional_price, + total_price=subitem.total_price + ) + ) + + for association in associations: + eatery_id = EateryID(association.category.menu.eatery_id) + menu_id = association.category.menu_id + category = association.category.category + if eatery_id not in eatery_menus_categories_map: + eatery_menus_categories_map[eatery_id] = {} + if menu_id not in eatery_menus_categories_map[eatery_id]: + eatery_menus_categories_map[eatery_id][menu_id] = {} + if category not in eatery_menus_categories_map[eatery_id][menu_id]: + eatery_menus_categories_map[eatery_id][menu_id][category] = [] + item_sections = None + if association.item.id in item_subitem_map: + item_sections = [] + for section in item_subitem_map[association.item.id]: + item_sections.append(MenuItemSection(section, item_subitem_map[association.item.id][section])) + + eatery_menus_categories_map[eatery_id][menu_id][category].append( + MenuItem( + name = association.item.name, + healthy = None, + base_price = association.item.base_price, + description = association.item.description, + sections = item_sections + ) + ) + + eatery_menus_map = {} + for eatery_id in eatery_menus_categories_map: + eatery_menus_map[eatery_id] = {} + for menu_id in eatery_menus_categories_map[eatery_id]: + categories = [] + for category in eatery_menus_categories_map[eatery_id][menu_id]: + categories.append( + MenuCategory( + category=category, + items = eatery_menus_categories_map[eatery_id][menu_id][category] + ) + ) + eatery_menus_map[eatery_id][menu_id] = Menu( + categories=categories + ) + self.cache["menus"] = eatery_menus_map + return self.child(*args, **kwargs) + + def description(self): + return "EateryStubs" diff --git a/api/dfg/schedule/ClosedSchedule.py b/api/dfg/schedule/ClosedSchedule.py index cb60b82..f435182 100644 --- a/api/dfg/schedule/ClosedSchedule.py +++ b/api/dfg/schedule/ClosedSchedule.py @@ -4,9 +4,10 @@ class ClosedSchedule(DfgNode): - def __init__(self, eatery_id: EateryID, child: DfgNode): + def __init__(self, eatery_id: EateryID, child: DfgNode, cache): self.eatery_id = eatery_id self.child = child + self.cache = cache def __call__(self, *args, **kwargs) -> list[Eatery]: # ClosedEventSchedule.objects.all() diff --git a/api/dfg/schedule/DateSchedule.py b/api/dfg/schedule/DateSchedule.py index 6595b85..738eaa0 100644 --- a/api/dfg/schedule/DateSchedule.py +++ b/api/dfg/schedule/DateSchedule.py @@ -4,8 +4,9 @@ class DateSchedule(DfgNode): - def __init__(self, eatery_id: EateryID): + def __init__(self, eatery_id: EateryID, cache): self.eatery_id = eatery_id + self.cache = cache def __call__(self, *args, **kwargs) -> list[Eatery]: # DateEventSchedule.objects.all() diff --git a/api/dfg/schedule/DayOfWeekSchedule.py b/api/dfg/schedule/DayOfWeekSchedule.py index 829572c..b491526 100644 --- a/api/dfg/schedule/DayOfWeekSchedule.py +++ b/api/dfg/schedule/DayOfWeekSchedule.py @@ -1,16 +1,39 @@ +from api.datatype.Event import Event +from api.datatype.Menu import Menu from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID -# from eateries.models import DayOfWeekEventSchedule +from eateries.models import DayOfWeekEventSchedule, MenuStore +from datetime import timedelta, datetime + +import pytz class DayOfWeekSchedule(DfgNode): - def __init__(self, eatery_id: EateryID): + def __init__(self, eatery_id: EateryID, cache): self.eatery_id = eatery_id + self.cache = cache def __call__(self, *args, **kwargs) -> list[Eatery]: - # DayOfWeekEventSchedule.objects.all() - return [] + if "day_of_week_schedules" not in self.cache: + self.cache["day_of_week_schedules"] = DayOfWeekEventSchedule.objects.all().values() + + tz = pytz.timezone('America/New_York') + schedules = [sched for sched in self.cache["day_of_week_schedules"] if EateryID(sched["eatery_id"]) == self.eatery_id] + events = [] + date = kwargs.get("start") + while date <= kwargs.get("end"): + day_schedule = [sched for sched in schedules if sched["day_of_week"] == date.strftime("%A")] + for sched in day_schedule: + events.append(Event( + description=sched["event_description"], + canonical_date=date, + start_timestamp=Event.combined_timestamp(date, sched["start"], tz), + end_timestamp=Event.combined_timestamp(date, sched["end"], tz), + menu=self.cache["menus"][self.eatery_id][sched["menu_id"]] + )) + date += timedelta(days = 1) + return events def description(self): return "DayOfWeekSchedule" diff --git a/api/dfg/wait_times/WaitTimes.py b/api/dfg/wait_times/WaitTimes.py index e172e62..10a3b32 100644 --- a/api/dfg/wait_times/WaitTimes.py +++ b/api/dfg/wait_times/WaitTimes.py @@ -4,11 +4,13 @@ from django.db.models import Avg from api.datatype.Eatery import Eatery, EateryID +from api.datatype.Event import Event from api.datatype.WaitTime import WaitTime from api.datatype.WaitTimesDay import WaitTimesDay from api.dfg.DfgNode import DfgNode from transactions.models import TransactionHistory +tz = pytz.timezone('America/New_York') class WaitTimes(DfgNode): @@ -96,7 +98,7 @@ def generate_eatery_wait_times_by_day( customers_waiting_in_line.append(0.0) block_end_time = transactions[index]['block_end_time'] - timestamp = int(WaitTimes.timestamp_combined(date, block_end_time) - 5 * 60 / 2) + timestamp = int(Event.combined_timestamp(date, block_end_time, tz) - 5 * 60 / 2) wait_times_data.insert(0, WaitTime( timestamp=timestamp, wait_time_low=wait_time_low, @@ -106,15 +108,6 @@ def generate_eatery_wait_times_by_day( return WaitTimesDay(canonical_date=date, data=wait_times_data) - @staticmethod - def timestamp_combined(date: datetime.date, time: datetime.time): - """ - Returns the Unix (UTC) timestamp of the combined (date, time) in the - New York timezone. - """ - - tz = pytz.timezone('America/New_York') - return int(tz.localize(datetime.combine(date, time)).timestamp()) def description(self): return "WaitTimes" diff --git a/eateries/models.py b/eateries/models.py index cf97678..360398d 100644 --- a/eateries/models.py +++ b/eateries/models.py @@ -93,6 +93,9 @@ class DayOfTheWeek(models.TextChoices): day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=10) start = models.TimeField() end = models.TimeField() + class Meta: + unique_together = ('eatery', 'day_of_week', 'event_description') + class DateEventSchedule(EventSchedule): event_description = models.CharField(choices=EventDescription.choices, max_length = 10) diff --git a/eateries/util.py b/eateries/util.py index e24c9ea..d91b3dd 100644 --- a/eateries/util.py +++ b/eateries/util.py @@ -12,6 +12,4 @@ class SnapshotFileName(Enum): DAY_OF_WEEK_EVENT_SCHEDULE = "day_of_week_event_schedule.txt" DATE_EVENT_SCHEDULE = "date_event_schedule.txt" CLOSED_EVENT_SCHEDULE = "closed_event_schedule.txt" - TRANSACTION_HISTORY = "transaction_history.txt" - - \ No newline at end of file + TRANSACTION_HISTORY = "transaction_history.txt" \ No newline at end of file From 771dee33535e2e6413852e3628dc31235e51307b Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 10 Jan 2022 15:59:12 -0500 Subject: [PATCH 038/305] Add comment --- api/dfg/schedule/CacheMenuInjection.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/dfg/schedule/CacheMenuInjection.py b/api/dfg/schedule/CacheMenuInjection.py index 726646e..5ee53d5 100644 --- a/api/dfg/schedule/CacheMenuInjection.py +++ b/api/dfg/schedule/CacheMenuInjection.py @@ -16,14 +16,14 @@ def __init__(self, child: DfgNode, cache): def __call__(self, *args, **kwargs) -> list[Eatery]: if "menus" not in self.cache: - eatery_menus_categories_map = {} associations = CategoryItemAssociation.objects \ .select_related("item") \ .select_related("category") \ .select_related("category__menu") \ - .all() - + .all() subitems = SubItemStore.objects.all() + + eatery_menus_categories_map = {} item_subitem_map = {} for subitem in subitems: item_id = subitem.item_id @@ -55,7 +55,6 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: item_sections = [] for section in item_subitem_map[association.item.id]: item_sections.append(MenuItemSection(section, item_subitem_map[association.item.id][section])) - eatery_menus_categories_map[eatery_id][menu_id][category].append( MenuItem( name = association.item.name, @@ -65,7 +64,6 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: sections = item_sections ) ) - eatery_menus_map = {} for eatery_id in eatery_menus_categories_map: eatery_menus_map[eatery_id] = {} @@ -82,6 +80,8 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: categories=categories ) self.cache["menus"] = eatery_menus_map + + # self.cache["menus"] is a map of form {[eatery_id]: {[menu_id]: [Menu]}} return self.child(*args, **kwargs) def description(self): From 6d830c3b1defc18344ad36ddf2349f5086cd73f3 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 10 Jan 2022 15:59:50 -0500 Subject: [PATCH 039/305] Another clean up line --- api/dfg/schedule/CacheMenuInjection.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/dfg/schedule/CacheMenuInjection.py b/api/dfg/schedule/CacheMenuInjection.py index 5ee53d5..a339102 100644 --- a/api/dfg/schedule/CacheMenuInjection.py +++ b/api/dfg/schedule/CacheMenuInjection.py @@ -5,8 +5,7 @@ from api.datatype.MenuSubItem import MenuSubItem from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID -from eateries.models import MenuStore, CategoryItemAssociation, SubItemStore -# from eateries.models import DateEventSchedule +from eateries.models import CategoryItemAssociation, SubItemStore class CacheMenuInjection(DfgNode): @@ -22,7 +21,7 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: .select_related("category__menu") \ .all() subitems = SubItemStore.objects.all() - + eatery_menus_categories_map = {} item_subitem_map = {} for subitem in subitems: From 78b9f7c1855d0a6cb3d22deae32f383c9f42c18f Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 10 Jan 2022 16:08:06 -0500 Subject: [PATCH 040/305] Update serializers to support dayofweek events --- eateries/management/commands/create_db_snapshot.py | 4 ++-- eateries/management/commands/ingest_db_snapshot.py | 2 +- eateries/serializers.py | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/eateries/management/commands/create_db_snapshot.py b/eateries/management/commands/create_db_snapshot.py index 538b266..037ac9e 100644 --- a/eateries/management/commands/create_db_snapshot.py +++ b/eateries/management/commands/create_db_snapshot.py @@ -52,8 +52,8 @@ def handle(self, *args, **options): # closed_event_schedules = models.ClosedEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) # self.write_to_file(closed_event_schedules, f"{folder_path}/{SnapshotFileName.CLOSED_EVENT_SCHEDULE}") - # day_of_week_event_schedules = models.DayOfWeekEventSchedule.objects.all() - # self.write_to_file(day_of_week_event_schedules, f"{folder_path}/{SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE}") + day_of_week_event_schedules = models.DayOfWeekEventSchedule.objects.all() + self.write_to_file(serializers.DayOfWeekEventScheduleSerializer(day_of_week_event_schedules, many=True), f"{folder_path}/{SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE}") # # TODO: Need to filter here for only the valid schedules # event_schedules = models.EventSchedule.objects.all() diff --git a/eateries/management/commands/ingest_db_snapshot.py b/eateries/management/commands/ingest_db_snapshot.py index f206414..445bc41 100644 --- a/eateries/management/commands/ingest_db_snapshot.py +++ b/eateries/management/commands/ingest_db_snapshot.py @@ -30,7 +30,7 @@ def handle(self, *args, **options): self.ingest_data(serializers.ItemStoreSerializer, SnapshotFileName.ITEM_STORE) self.ingest_data(serializers.SubItemStoreSerializer, SnapshotFileName.SUBITEM_STORE) self.ingest_data(serializers.CategoryItemAssociationSerializer, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) - + self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) # with open(f"{folder_path}/{SnapshotFileName.EXCEPTION_STORE.value}", "r") as file: # serialized_objs = [] diff --git a/eateries/serializers.py b/eateries/serializers.py index c05f1ea..8a9934c 100644 --- a/eateries/serializers.py +++ b/eateries/serializers.py @@ -35,4 +35,9 @@ class Meta: class CategoryItemAssociationSerializer(serializers.ModelSerializer): class Meta: model = models.CategoryItemAssociation + fields = '__all__' + +class DayOfWeekEventScheduleSerializer(serializers.ModelSerializer): + class Meta: + model = models.DayOfWeekEventSchedule fields = '__all__' \ No newline at end of file From de5794948b37f9b6106b812f9c368763e1c3fb52 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 10 Jan 2022 16:23:22 -0500 Subject: [PATCH 041/305] Removing print statements --- .gitignore | 1 + api/dfg/wait_times/WaitTimes.py | 2 +- eateries/management/commands/create_db_snapshot.py | 2 +- transactions/management/commands/fetch_recent_transactions.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 81bcab5..ae6a0a5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ __pycache__/ *$py.class # More Python stuff +.DS_Store env/ .Python build/ diff --git a/api/dfg/wait_times/WaitTimes.py b/api/dfg/wait_times/WaitTimes.py index 10a3b32..11690af 100644 --- a/api/dfg/wait_times/WaitTimes.py +++ b/api/dfg/wait_times/WaitTimes.py @@ -88,7 +88,7 @@ def generate_eatery_wait_times_by_day( prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) if prev_bucket_guest_arrival > 9: # TODO: Send a slack error here instead - print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") + # print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") else: customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index]["transaction_avg"] num_customers = customers_waiting_in_line.pop(0) diff --git a/eateries/management/commands/create_db_snapshot.py b/eateries/management/commands/create_db_snapshot.py index 037ac9e..93905aa 100644 --- a/eateries/management/commands/create_db_snapshot.py +++ b/eateries/management/commands/create_db_snapshot.py @@ -53,7 +53,7 @@ def handle(self, *args, **options): # self.write_to_file(closed_event_schedules, f"{folder_path}/{SnapshotFileName.CLOSED_EVENT_SCHEDULE}") day_of_week_event_schedules = models.DayOfWeekEventSchedule.objects.all() - self.write_to_file(serializers.DayOfWeekEventScheduleSerializer(day_of_week_event_schedules, many=True), f"{folder_path}/{SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE}") + self.write_to_file(serializers.DayOfWeekEventScheduleSerializer(day_of_week_event_schedules, many=True), f"{folder_path}/{SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE.value}") # # TODO: Need to filter here for only the valid schedules # event_schedules = models.EventSchedule.objects.all() diff --git a/transactions/management/commands/fetch_recent_transactions.py b/transactions/management/commands/fetch_recent_transactions.py index f65018f..b264bf9 100644 --- a/transactions/management/commands/fetch_recent_transactions.py +++ b/transactions/management/commands/fetch_recent_transactions.py @@ -23,4 +23,4 @@ def handle(self, *args, **options): res = UpdateTransactionsController(resp.json()).process() if res["success"]: num_inserted = res["result"]["num_inserted"] - print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file + # print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file From f317088dd3268468c427843997725a10008e187f Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Mon, 10 Jan 2022 16:24:01 -0500 Subject: [PATCH 042/305] Fix indentation --- api/dfg/util/InMemoryCache.py | 4 ++-- api/dfg/wait_times/WaitTimes.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/api/dfg/util/InMemoryCache.py b/api/dfg/util/InMemoryCache.py index 35b5b5f..1f3b9ee 100644 --- a/api/dfg/util/InMemoryCache.py +++ b/api/dfg/util/InMemoryCache.py @@ -53,10 +53,10 @@ def __call__(self, *args, **kwargs): should_reload = kwargs.get("reload") for snapshot in self.snapshots: if not should_reload and snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): - print(f"{self}: Returning from cache") + # print(f"{self}: Returning from cache") return snapshot.get_data() - print(f"{self}: Fetching data") + # print(f"{self}: Fetching data") new_snapshot = DataSnapshot(args, kwargs, self.child(*args, **kwargs), self.current_time()) if len(self.snapshots) < self.max_size: self.snapshots.append(new_snapshot) diff --git a/api/dfg/wait_times/WaitTimes.py b/api/dfg/wait_times/WaitTimes.py index 11690af..b96f878 100644 --- a/api/dfg/wait_times/WaitTimes.py +++ b/api/dfg/wait_times/WaitTimes.py @@ -87,6 +87,7 @@ def generate_eatery_wait_times_by_day( how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index]["transaction_avg"] prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) if prev_bucket_guest_arrival > 9: + pass # TODO: Send a slack error here instead # print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") else: From 638c994e0c64ade1b27850ec7b97815e6073a316 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Tue, 11 Jan 2022 08:56:52 -0500 Subject: [PATCH 043/305] Address PR comments --- api/datatype/Eatery.py | 36 ++-- .../{EateryException.py => EateryAlert.py} | 14 +- api/datatype/Event.py | 19 +-- api/datatype/MenuSubItem.py | 4 +- api/dfg/CornellDiningNow.py | 2 +- api/dfg/EateriesFromDB.py | 34 ++-- api/dfg/macros/LeftMergeEateries.py | 6 +- api/dfg/macros/LeftMergeEvents.py | 6 +- api/dfg/schedule/CornellDiningEvents.py | 2 +- api/dfg/schedule/DayOfWeekSchedule.py | 10 +- api/dfg/{util => system}/ConvertFromJson.py | 0 api/dfg/{util => system}/ConvertToJson.py | 0 .../{util => system}/DictResponseWrapper.py | 0 api/dfg/{util => system}/EateryGenerator.py | 0 api/dfg/{util => system}/InMemoryCache.py | 2 +- api/dfg/{util => system}/LeftMerge.py | 0 api/dfg/{util => system}/Mapping.py | 0 api/dfg/wait_times/WaitTimes.py | 11 +- api/util.py | 71 -------- api/views.py | 10 +- eateries/admin.py | 2 +- .../management/commands/create_db_snapshot.py | 24 +-- .../management/commands/ingest_db_snapshot.py | 19 +-- ...name_exceptionstore_alertstore_and_more.py | 21 +++ eateries/models.py | 2 +- eateries/serializers.py | 4 +- eateries/util.py | 15 -- .../update_transactions_controller.py | 87 +--------- util/constants.py | 158 ++++++++++++++++++ util/time.py | 5 + 30 files changed, 281 insertions(+), 283 deletions(-) rename api/datatype/{EateryException.py => EateryAlert.py} (63%) rename api/dfg/{util => system}/ConvertFromJson.py (100%) rename api/dfg/{util => system}/ConvertToJson.py (100%) rename api/dfg/{util => system}/DictResponseWrapper.py (100%) rename api/dfg/{util => system}/EateryGenerator.py (100%) rename api/dfg/{util => system}/InMemoryCache.py (97%) rename api/dfg/{util => system}/LeftMerge.py (100%) rename api/dfg/{util => system}/Mapping.py (100%) delete mode 100644 api/util.py create mode 100644 eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py delete mode 100644 eateries/util.py create mode 100644 util/constants.py create mode 100644 util/time.py diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index 6deaaa0..767987e 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -3,7 +3,7 @@ from enum import Enum import pytz -from api.datatype.EateryException import EateryException +from api.datatype.EateryAlert import EateryAlert from api.datatype.Event import Event, filter_range from api.datatype.WaitTimesDay import WaitTimesDay @@ -66,7 +66,7 @@ def __init__( online_order: Optional[bool] = None, online_order_url: Optional[str] = None, wait_times: Optional[list[WaitTimesDay]] = None, - exceptions: Optional[list[EateryException]] = None + alerts: Optional[list[EateryAlert]] = None ): self.id = id self.name = name @@ -81,7 +81,7 @@ def __init__( self.online_order = online_order self.online_order_url = online_order_url self.wait_times = wait_times - self.exceptions = exceptions + self.alerts = alerts def events( self, @@ -113,34 +113,30 @@ def to_json( "online_order": self.online_order, "online_order_url": self.online_order_url, "wait_times": None if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times], - "exceptions": None if self.exceptions is None else [exception.to_json() for exception in self.exceptions] + "alerts": None if self.alerts is None else [alert.to_json() for alert in self.alerts] } return eatery - @staticmethod - def parse_field(json, key): - return None if key not in json else json[key] - @staticmethod def from_json(eatery_json): return Eatery( id=None if "id" not in eatery_json else EateryID(eatery_json["id"]), - name=Eatery.parse_field(eatery_json, "name"), - image_url=Eatery.parse_field(eatery_json, "image_url"), - menu_summary=Eatery.parse_field(eatery_json, "menu_summary"), - campus_area=Eatery.parse_field(eatery_json, "campus_area"), + name=eatery_json.get("name"), + image_url=eatery_json.get("image_url"), + menu_summary=eatery_json.get("menu_summary"), + campus_area=eatery_json.get("campus_area"), events=None if "events" not in eatery_json or eatery_json["events"] is None else [Event.from_json(event) for event in eatery_json["events"]], - latitude=Eatery.parse_field(eatery_json, "latitude"), - longitude=Eatery.parse_field(eatery_json, "longitude"), - payment_methods=Eatery.parse_field(eatery_json, "payment_methods"), - location=Eatery.parse_field(eatery_json, "location"), - online_order=Eatery.parse_field(eatery_json, "online_order"), - online_order_url=Eatery.parse_field(eatery_json, "online_order_url"), + latitude=eatery_json.get("latitude"), + longitude=eatery_json.get("longitude"), + payment_methods=eatery_json.get("payment_methods"), + location=eatery_json.get("location"), + online_order=eatery_json.get("online_order"), + online_order_url=eatery_json.get("online_order_url"), wait_times=None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]], - exceptions=None if "exceptions" not in eatery_json or eatery_json["exceptions"] is None - else [EateryException.from_json(exception) for exception in eatery_json["exceptions"]] + alerts=None if "alerts" not in eatery_json or eatery_json["alerts"] is None + else [EateryAlert.from_json(alert) for alert in eatery_json["alerts"]] ) def clone(self): diff --git a/api/datatype/EateryException.py b/api/datatype/EateryAlert.py similarity index 63% rename from api/datatype/EateryException.py rename to api/datatype/EateryAlert.py index 656ee6d..2bb511b 100644 --- a/api/datatype/EateryException.py +++ b/api/datatype/EateryAlert.py @@ -1,4 +1,4 @@ -class EateryException: +class EateryAlert: def __init__( self, @@ -21,10 +21,10 @@ def to_json(self): } @staticmethod - def from_json(exception_json): - return EateryException( - id = exception_json["id"], - description=exception_json["description"], - start_timestamp=exception_json["start_timestamp"], - end_timestamp=exception_json["end_timestamp"] + def from_json(alert_json): + return EateryAlert( + id = alert_json["id"], + description=alert_json["description"], + start_timestamp=alert_json["start_timestamp"], + end_timestamp=alert_json["end_timestamp"] ) diff --git a/api/datatype/Event.py b/api/datatype/Event.py index c0c6dbe..9d3afcf 100644 --- a/api/datatype/Event.py +++ b/api/datatype/Event.py @@ -1,6 +1,7 @@ from typing import Optional -from datetime import date, time, datetime +from datetime import date, time from api.datatype.Menu import Menu +from util.time import combined_timestamp import pytz @@ -48,16 +49,6 @@ def from_json(event_json): def __contains__(self, item: int): return self.start_timestamp <= item <= self.end_timestamp - #TODO: Restructure to move to some util file into major folder outside of api - @staticmethod - def combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: - """ - Returns the Unix (UTC) timestamp of the combined (date, time) in the - New York timezone. - """ - return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) - - def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): if events is None: return [] @@ -66,14 +57,14 @@ def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Op return events elif tzinfo is not None and start is not None and end is None: - start_ts = Event.combined_timestamp(start, time(), tzinfo) + start_ts = combined_timestamp(start, time(), tzinfo) return [event for event in events if ( (start_ts in event) or start == event.canonical_date )] elif tzinfo is not None and start is not None and end is not None: - start_ts = Event.combined_timestamp(start, time(), tzinfo) - end_ts = Event.combined_timestamp(end, time(), tzinfo) + start_ts = combined_timestamp(start, time(), tzinfo) + end_ts = combined_timestamp(end, time(), tzinfo) return [event for event in events if ( (start_ts in event) or (end_ts in event) or start <= event.canonical_date <= end )] diff --git a/api/datatype/MenuSubItem.py b/api/datatype/MenuSubItem.py index 9fbfe02..854b0ed 100644 --- a/api/datatype/MenuSubItem.py +++ b/api/datatype/MenuSubItem.py @@ -23,6 +23,6 @@ def to_json(self): def from_json(item_json): return MenuSubItem( name=item_json["name"], - total_price=None if "total_price" not in item_json else item_json["total_price"], - additional_price=None if "additional_price" not in item_json else item_json["additional_price"] + total_price=item_json.get("total_price"), + additional_price=item_json.get("additional_price") ) diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py index 13293a4..de104d7 100644 --- a/api/dfg/CornellDiningNow.py +++ b/api/dfg/CornellDiningNow.py @@ -3,7 +3,7 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery -from api.util import dining_id_to_internal_id, CORNELL_DINING_URL +from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL from datetime import date diff --git a/api/dfg/EateriesFromDB.py b/api/dfg/EateriesFromDB.py index 6402385..d474e66 100644 --- a/api/dfg/EateriesFromDB.py +++ b/api/dfg/EateriesFromDB.py @@ -5,15 +5,15 @@ import pytz from api.datatype.Eatery import Eatery, EateryID -from api.datatype.EateryException import EateryException +from api.datatype.EateryAlert import EateryAlert from api.datatype.Event import Event from api.datatype.Menu import Menu from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem from api.dfg.DfgNode import DfgNode -from eateries.models import EateryStore, ExceptionStore -from eateries.serializers import EateryStoreSerializer, ExceptionStoreSerializer +from eateries.models import EateryStore, AlertStore +from eateries.serializers import EateryStoreSerializer, AlertStoreSerializer from datetime import datetime @@ -25,17 +25,17 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: eateries = EateryStore.objects.all() serialized_eateries = EateryStoreSerializer(data=eateries, many=True) serialized_eateries.is_valid() - exceptions = ExceptionStore.objects.filter(end_timestamp__gte=datetime.now().timestamp(), start_timestamp__lte=datetime.now().timestamp()) - serialized_exceptions = ExceptionStoreSerializer(data=exceptions, many=True) - serialized_exceptions.is_valid() - return list(map(lambda x: EateriesFromDB.eatery_from_serialized(x, serialized_exceptions.data), serialized_eateries.data)) + alerts = AlertStore.objects.filter(end_timestamp__gte=datetime.now().timestamp(), start_timestamp__lte=datetime.now().timestamp()) + serialized_alerts = AlertStoreSerializer(data=alerts, many=True) + serialized_alerts.is_valid() + return list(map(lambda x: EateriesFromDB.eatery_from_serialized(x, serialized_alerts.data), serialized_eateries.data)) @staticmethod def none_repr(str): return None if str == None or len(str) == 0 else str @staticmethod - def eatery_from_serialized(serialized_eatery: dict, serialized_exceptions: list[dict]) -> Eatery: + def eatery_from_serialized(serialized_eatery: dict, serialized_alerts: list[dict]) -> Eatery: return Eatery( id=EateryID(serialized_eatery["id"]), name=EateriesFromDB.none_repr(serialized_eatery["name"]), @@ -49,7 +49,7 @@ def eatery_from_serialized(serialized_eatery: dict, serialized_exceptions: list[ location=EateriesFromDB.none_repr(serialized_eatery["location"]), online_order=serialized_eatery["online_order"], online_order_url=EateriesFromDB.none_repr(serialized_eatery["online_order_url"]), - exceptions = EateriesFromDB.exceptions(serialized_eatery["id"], serialized_exceptions) + alerts = EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts) ) @staticmethod @@ -64,16 +64,16 @@ def payment_methods(serialized_eatery:dict): return pay_methods @staticmethod - def exceptions(eatery_id: int, serialized_exceptions: list[dict]): - return [EateriesFromDB.exception_from_serialized(exception) for exception in serialized_exceptions if exception["eatery"] == eatery_id] + def alerts(eatery_id: int, serialized_alerts: list[dict]): + return [EateriesFromDB.alert_from_serialized(alert) for alert in serialized_alerts if alert["eatery"] == eatery_id] @staticmethod - def exception_from_serialized(serialized_exception: dict): - return EateryException( - id=serialized_exception["id"], - description=serialized_exception["description"], - start_timestamp=serialized_exception["start_timestamp"], - end_timestamp=serialized_exception["end_timestamp"] + def alert_from_serialized(serialized_alert: dict): + return EateryAlert( + id=serialized_alert["id"], + description=serialized_alert["description"], + start_timestamp=serialized_alert["start_timestamp"], + end_timestamp=serialized_alert["end_timestamp"] ) diff --git a/api/dfg/macros/LeftMergeEateries.py b/api/dfg/macros/LeftMergeEateries.py index 00ec52f..92f5e87 100644 --- a/api/dfg/macros/LeftMergeEateries.py +++ b/api/dfg/macros/LeftMergeEateries.py @@ -1,7 +1,7 @@ from api.dfg.DfgNode import DfgNode -from api.dfg.util.ConvertToJson import ConvertToJson -from api.dfg.util.ConvertFromJson import EateryFromJson -from api.dfg.util.LeftMerge import LeftMerge +from api.dfg.system.ConvertToJson import ConvertToJson +from api.dfg.system.ConvertFromJson import EateryFromJson +from api.dfg.system.LeftMerge import LeftMerge class LeftMergeEateries(DfgNode): diff --git a/api/dfg/macros/LeftMergeEvents.py b/api/dfg/macros/LeftMergeEvents.py index f3adda3..264f940 100644 --- a/api/dfg/macros/LeftMergeEvents.py +++ b/api/dfg/macros/LeftMergeEvents.py @@ -1,8 +1,8 @@ from api.dfg.DfgNode import DfgNode -from api.dfg.util.ConvertToJson import ConvertToJson -from api.dfg.util.ConvertFromJson import EventFromJson -from api.dfg.util.LeftMerge import LeftMerge +from api.dfg.system.ConvertToJson import ConvertToJson +from api.dfg.system.ConvertFromJson import EventFromJson +from api.dfg.system.LeftMerge import LeftMerge # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if # conflict) diff --git a/api/dfg/schedule/CornellDiningEvents.py b/api/dfg/schedule/CornellDiningEvents.py index f68fcf3..9bfc206 100644 --- a/api/dfg/schedule/CornellDiningEvents.py +++ b/api/dfg/schedule/CornellDiningEvents.py @@ -4,7 +4,7 @@ from api.datatype.Menu import Menu from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem -from api.util import dining_id_to_internal_id, CORNELL_DINING_URL +from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL from datetime import date import requests diff --git a/api/dfg/schedule/DayOfWeekSchedule.py b/api/dfg/schedule/DayOfWeekSchedule.py index b491526..2966806 100644 --- a/api/dfg/schedule/DayOfWeekSchedule.py +++ b/api/dfg/schedule/DayOfWeekSchedule.py @@ -1,10 +1,10 @@ from api.datatype.Event import Event -from api.datatype.Menu import Menu from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID -from eateries.models import DayOfWeekEventSchedule, MenuStore +from eateries.models import DayOfWeekEventSchedule from datetime import timedelta, datetime +from util.time import combined_timestamp import pytz @@ -17,8 +17,6 @@ def __init__(self, eatery_id: EateryID, cache): def __call__(self, *args, **kwargs) -> list[Eatery]: if "day_of_week_schedules" not in self.cache: self.cache["day_of_week_schedules"] = DayOfWeekEventSchedule.objects.all().values() - - tz = pytz.timezone('America/New_York') schedules = [sched for sched in self.cache["day_of_week_schedules"] if EateryID(sched["eatery_id"]) == self.eatery_id] events = [] date = kwargs.get("start") @@ -28,8 +26,8 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: events.append(Event( description=sched["event_description"], canonical_date=date, - start_timestamp=Event.combined_timestamp(date, sched["start"], tz), - end_timestamp=Event.combined_timestamp(date, sched["end"], tz), + start_timestamp=combined_timestamp(date=date, time=sched["start"], tzinfo=kwargs.get("tzinfo")), + end_timestamp=combined_timestamp(date=date, time=sched["end"], tzinfo=kwargs.get("tzinfo")), menu=self.cache["menus"][self.eatery_id][sched["menu_id"]] )) date += timedelta(days = 1) diff --git a/api/dfg/util/ConvertFromJson.py b/api/dfg/system/ConvertFromJson.py similarity index 100% rename from api/dfg/util/ConvertFromJson.py rename to api/dfg/system/ConvertFromJson.py diff --git a/api/dfg/util/ConvertToJson.py b/api/dfg/system/ConvertToJson.py similarity index 100% rename from api/dfg/util/ConvertToJson.py rename to api/dfg/system/ConvertToJson.py diff --git a/api/dfg/util/DictResponseWrapper.py b/api/dfg/system/DictResponseWrapper.py similarity index 100% rename from api/dfg/util/DictResponseWrapper.py rename to api/dfg/system/DictResponseWrapper.py diff --git a/api/dfg/util/EateryGenerator.py b/api/dfg/system/EateryGenerator.py similarity index 100% rename from api/dfg/util/EateryGenerator.py rename to api/dfg/system/EateryGenerator.py diff --git a/api/dfg/util/InMemoryCache.py b/api/dfg/system/InMemoryCache.py similarity index 97% rename from api/dfg/util/InMemoryCache.py rename to api/dfg/system/InMemoryCache.py index 1f3b9ee..afd6ea9 100644 --- a/api/dfg/util/InMemoryCache.py +++ b/api/dfg/system/InMemoryCache.py @@ -3,7 +3,7 @@ from api.dfg.DfgNode import DfgNode from typing import Optional -from api.dfg.util.ConvertToJson import ConvertToJson +from api.dfg.system.ConvertToJson import ConvertToJson class DataSnapshot: diff --git a/api/dfg/util/LeftMerge.py b/api/dfg/system/LeftMerge.py similarity index 100% rename from api/dfg/util/LeftMerge.py rename to api/dfg/system/LeftMerge.py diff --git a/api/dfg/util/Mapping.py b/api/dfg/system/Mapping.py similarity index 100% rename from api/dfg/util/Mapping.py rename to api/dfg/system/Mapping.py diff --git a/api/dfg/wait_times/WaitTimes.py b/api/dfg/wait_times/WaitTimes.py index b96f878..369a9e6 100644 --- a/api/dfg/wait_times/WaitTimes.py +++ b/api/dfg/wait_times/WaitTimes.py @@ -1,4 +1,4 @@ -from datetime import date, datetime, timedelta +from datetime import date, timedelta import pytz from django.db.models import Avg @@ -10,7 +10,7 @@ from api.dfg.DfgNode import DfgNode from transactions.models import TransactionHistory -tz = pytz.timezone('America/New_York') +from util.time import combined_timestamp class WaitTimes(DfgNode): @@ -38,7 +38,7 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: eatery_wait_times = [] for date in self.cache["transactions"]: eatery_transaction_avgs = [transaction_avg for transaction_avg in self.cache["transactions"][date] if transaction_avg["eatery_id"] == self.eatery_id.value] - eatery_wait_times.append(WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs)) + eatery_wait_times.append(WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs, kwargs.get("tzinfo"))) return eatery_wait_times @@ -76,7 +76,8 @@ def base_time_to_get_food(eatery_id: EateryID) -> list[int]: def generate_eatery_wait_times_by_day( eatery_id: EateryID, date: date, - transactions: list + transactions: list, + tzinfo: pytz.tzinfo ) -> WaitTimesDay: wait_times_data = [] customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] @@ -99,7 +100,7 @@ def generate_eatery_wait_times_by_day( customers_waiting_in_line.append(0.0) block_end_time = transactions[index]['block_end_time'] - timestamp = int(Event.combined_timestamp(date, block_end_time, tz) - 5 * 60 / 2) + timestamp = int(combined_timestamp(date, block_end_time, tzinfo) - 5 * 60 / 2) wait_times_data.insert(0, WaitTime( timestamp=timestamp, wait_time_low=wait_time_low, diff --git a/api/util.py b/api/util.py deleted file mode 100644 index 305e3e5..0000000 --- a/api/util.py +++ /dev/null @@ -1,71 +0,0 @@ -from api.datatype.Eatery import EateryID - -CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" - -def dining_id_to_internal_id(id: int): - if id == 31: - return EateryID.ONE_ZERO_FOUR_WEST - elif id == 7: - return EateryID.LIBE_CAFE - elif id == 8: - return EateryID.ATRIUM_CAFE - elif id == 1: - return EateryID.BEAR_NECESSITIES - elif id == 25: - return EateryID.BECKER_HOUSE - elif id == 10: - return EateryID.BIG_RED_BARN - elif id == 11: - return EateryID.BUS_STOP_BAGELS - elif id == 12: - return EateryID.CAFE_JENNIE - elif id == 2: - return EateryID.CAROLS_CAFE - elif id == 26: - return EateryID.COOK_HOUSE - elif id == 14: - return EateryID.DAIRY_BAR - elif id == 41: - return EateryID.CROSSINGS_CAFE - elif id == 32: - return EateryID.FRANNYS - elif id == 16: - return EateryID.GOLDIES_CAFE - elif id == 15: - return EateryID.GREEN_DRAGON - elif id == 24: - return EateryID.HOT_DOG_CART - elif id == 34: - return EateryID.ICE_CREAM_BIKE - elif id == 27: - return EateryID.BETHE_HOUSE - elif id == 28: - return EateryID.JANSENS_MARKET - elif id == 29: - return EateryID.KEETON_HOUSE - elif id == 42: - return EateryID.MANN_CAFE - elif id == 18: - return EateryID.MARTHAS_CAFE - elif id == 19: - return EateryID.MATTINS_CAFE - elif id == 33: - return EateryID.MCCORMICKS - elif id == 3: - return EateryID.NORTH_STAR_DINING - elif id == 20: - return EateryID.OKENSHIELDS - elif id == 4: - return EateryID.RISLEY - elif id == 5: - return EateryID.RPCC - elif id == 30: - return EateryID.ROSE_HOUSE - elif id == 21: - return EateryID.RUSTYS - elif id == 13: - return EateryID.STRAIGHT_FROM_THE_MARKET - elif id == 23: - return EateryID.TRILLIUM - else: - return None \ No newline at end of file diff --git a/api/views.py b/api/views.py index c6f32ea..e44d9af 100644 --- a/api/views.py +++ b/api/views.py @@ -9,11 +9,11 @@ from api.dfg.EateriesFromDB import EateriesFromDB from api.dfg.macros.EateryEvents import EateryEvents -from api.dfg.util.DictResponseWrapper import DictResponseWrapper -from api.dfg.util.ConvertToJson import ConvertToJson -from api.dfg.util.EateryGenerator import EateryGenerator -from api.dfg.util.InMemoryCache import InMemoryCache -from api.dfg.util.Mapping import Mapping +from api.dfg.system.DictResponseWrapper import DictResponseWrapper +from api.dfg.system.ConvertToJson import ConvertToJson +from api.dfg.system.EateryGenerator import EateryGenerator +from api.dfg.system.InMemoryCache import InMemoryCache +from api.dfg.system.Mapping import Mapping from api.dfg.macros.LeftMergeEateries import LeftMergeEateries diff --git a/eateries/admin.py b/eateries/admin.py index 7f65f66..1a1a610 100644 --- a/eateries/admin.py +++ b/eateries/admin.py @@ -7,7 +7,7 @@ admin.site.register(models.MenuStore) admin.site.register(models.ItemStore) admin.site.register(models.SubItemStore) -admin.site.register(models.ExceptionStore) +admin.site.register(models.AlertStore) admin.site.register(models.CategoryStore) admin.site.register(models.CategoryItemAssociation) admin.site.register(models.DayOfWeekEventSchedule) diff --git a/eateries/management/commands/create_db_snapshot.py b/eateries/management/commands/create_db_snapshot.py index 93905aa..5d87397 100644 --- a/eateries/management/commands/create_db_snapshot.py +++ b/eateries/management/commands/create_db_snapshot.py @@ -4,7 +4,7 @@ from datetime import datetime from pathlib import Path -from eateries.util import SnapshotFileName +from util.constants import SnapshotFileName import eateries.models as models import eateries.serializers as serializers @@ -14,7 +14,9 @@ class Command(BaseCommand): help = 'Saves the current state of the database' - def write_to_file(self, serialized_lst, file_path): + def write_to_file(self, serializer, db_objects, folder_path, file_name_enum: SnapshotFileName): + file_path = f"{folder_path}/{file_name_enum.value}" + serialized_lst = serializer(db_objects, many=True) with open(file_path, "w") as file: for obj in serialized_lst.data: file.write(json.dumps(obj) + "\n") @@ -26,25 +28,25 @@ def handle(self, *args, **options): Path(folder_path).mkdir(parents=True, exist_ok=True) eateries = models.EateryStore.objects.all() - self.write_to_file(serializers.EateryStoreSerializer(eateries, many=True), f"{folder_path}/{SnapshotFileName.EATERY_STORE.value}") + self.write_to_file(serializers.EateryStoreSerializer, eateries, folder_path, SnapshotFileName.EATERY_STORE) - exceptions = models.ExceptionStore.objects.filter(end_timestamp__gte=datetime.now().timestamp()) - self.write_to_file(serializers.ExceptionStoreSerializer(exceptions, many=True), f"{folder_path}/{SnapshotFileName.EXCEPTION_STORE.value}") + alerts = models.AlertStore.objects.filter(end_timestamp__gte=datetime.now().timestamp()) + self.write_to_file(serializers.AlertStoreSerializer, alerts, folder_path, SnapshotFileName.ALERT_STORE) menus = models.MenuStore.objects.all() - self.write_to_file(serializers.MenuStoreSerializer(menus, many=True), f"{folder_path}/{SnapshotFileName.MENU_STORE.value}") + self.write_to_file(serializers.MenuStoreSerializer, menus, folder_path, SnapshotFileName.MENU_STORE) categories = models.CategoryStore.objects.all() - self.write_to_file(serializers.CategoryStoreSerializer(categories, many=True), f"{folder_path}/{SnapshotFileName.CATEGORY_STORE.value}") + self.write_to_file(serializers.CategoryStoreSerializer, categories, folder_path, SnapshotFileName.CATEGORY_STORE) items = models.ItemStore.objects.all() - self.write_to_file(serializers.ItemStoreSerializer(items, many=True), f"{folder_path}/{SnapshotFileName.ITEM_STORE.value}") + self.write_to_file(serializers.ItemStoreSerializer, items, folder_path, SnapshotFileName.ITEM_STORE) subitems = models.SubItemStore.objects.all() - self.write_to_file(serializers.SubItemStoreSerializer(subitems, many=True), f"{folder_path}/{SnapshotFileName.SUBITEM_STORE.value}") + self.write_to_file(serializers.SubItemStoreSerializer, subitems, folder_path, SnapshotFileName.SUBITEM_STORE) category_item_associations = models.CategoryItemAssociation.objects.all() - self.write_to_file(serializers.CategoryItemAssociationSerializer(category_item_associations, many=True), f"{folder_path}/{SnapshotFileName.CATEGORY_ITEM_ASSOCIATION.value}") + self.write_to_file(serializers.CategoryItemAssociationSerializer, category_item_associations, folder_path, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) # date_event_schedules = models.DateEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) # self.write_to_file(date_event_schedules, f"{folder_path}/{SnapshotFileName.DATE_EVENT_SCHEDULE}") @@ -53,7 +55,7 @@ def handle(self, *args, **options): # self.write_to_file(closed_event_schedules, f"{folder_path}/{SnapshotFileName.CLOSED_EVENT_SCHEDULE}") day_of_week_event_schedules = models.DayOfWeekEventSchedule.objects.all() - self.write_to_file(serializers.DayOfWeekEventScheduleSerializer(day_of_week_event_schedules, many=True), f"{folder_path}/{SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE.value}") + self.write_to_file(serializers.DayOfWeekEventScheduleSerializer, day_of_week_event_schedules, folder_path, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) # # TODO: Need to filter here for only the valid schedules # event_schedules = models.EventSchedule.objects.all() diff --git a/eateries/management/commands/ingest_db_snapshot.py b/eateries/management/commands/ingest_db_snapshot.py index 445bc41..ff7a8a0 100644 --- a/eateries/management/commands/ingest_db_snapshot.py +++ b/eateries/management/commands/ingest_db_snapshot.py @@ -1,10 +1,8 @@ from django.core.management.base import BaseCommand - -from eateries.util import SnapshotFileName +from util.constants import SnapshotFileName import eateries.serializers as serializers - import json class Command(BaseCommand): @@ -18,25 +16,16 @@ def ingest_data(self, serializer, file_name: SnapshotFileName): for line in file: if (len(line) > 2): json_objs.append(json.loads(line)) - serialized_objs = serializer(data=json_objs, many=True) + serialized_objs = serializer(data=json~_objs, many=True) serialized_objs.is_valid() serialized_objs.save() def handle(self, *args, **options): self.ingest_data(serializers.EateryStoreSerializer, SnapshotFileName.EATERY_STORE) - self.ingest_data(serializers.ExceptionStoreSerializer, SnapshotFileName.EXCEPTION_STORE) + self.ingest_data(serializers.AlertStoreSerializer, SnapshotFileName.ALERT_STORE) self.ingest_data(serializers.MenuStoreSerializer, SnapshotFileName.MENU_STORE) self.ingest_data(serializers.CategoryStoreSerializer, SnapshotFileName.CATEGORY_STORE) self.ingest_data(serializers.ItemStoreSerializer, SnapshotFileName.ITEM_STORE) self.ingest_data(serializers.SubItemStoreSerializer, SnapshotFileName.SUBITEM_STORE) self.ingest_data(serializers.CategoryItemAssociationSerializer, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) - self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) - - # with open(f"{folder_path}/{SnapshotFileName.EXCEPTION_STORE.value}", "r") as file: - # serialized_objs = [] - # for line in file: - # if (len(line) > 2): - # serialized_objs.append(json.loads(line)) - # exception_objs = serializers.ExceptionStoreSerializer(data=serialized_objs, many=True) - # exception_objs.is_valid() - # eatery_objs.save() \ No newline at end of file + self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) \ No newline at end of file diff --git a/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py b/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py new file mode 100644 index 0000000..e8d5d58 --- /dev/null +++ b/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0 on 2022-01-11 13:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0006_alter_categoryitemassociation_unique_together_and_more'), + ] + + operations = [ + migrations.RenameModel( + old_name='ExceptionStore', + new_name='AlertStore', + ), + migrations.AlterUniqueTogether( + name='dayofweekeventschedule', + unique_together={('eatery', 'day_of_week', 'event_description')}, + ), + ] diff --git a/eateries/models.py b/eateries/models.py index 360398d..de25063 100644 --- a/eateries/models.py +++ b/eateries/models.py @@ -22,7 +22,7 @@ class CampusArea(models.TextChoices): payment_accepts_brbs = models.BooleanField(null = True, blank=True) payment_accepts_cash = models.BooleanField(null = True, blank=True) -class ExceptionStore(models.Model): +class AlertStore(models.Model): id = models.IntegerField(primary_key=True) eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) description = models.CharField(max_length = 250) diff --git a/eateries/serializers.py b/eateries/serializers.py index 8a9934c..0dcc763 100644 --- a/eateries/serializers.py +++ b/eateries/serializers.py @@ -7,9 +7,9 @@ class Meta: model = models.EateryStore fields = '__all__' -class ExceptionStoreSerializer(serializers.ModelSerializer): +class AlertStoreSerializer(serializers.ModelSerializer): class Meta: - model = models.ExceptionStore + model = models.AlertStore fields = '__all__' class MenuStoreSerializer(serializers.ModelSerializer): diff --git a/eateries/util.py b/eateries/util.py deleted file mode 100644 index d91b3dd..0000000 --- a/eateries/util.py +++ /dev/null @@ -1,15 +0,0 @@ -from enum import Enum - -class SnapshotFileName(Enum): - EATERY_STORE = "eatery_store.txt" - EXCEPTION_STORE = "exception_store.txt" - CATEGORY_STORE = "category_store.txt" - MENU_STORE = "menu_store.txt" - ITEM_STORE = "item_store.txt" - SUBITEM_STORE = "subitem_store.txt" - CATEGORY_ITEM_ASSOCIATION = "category_item_association.txt" - EVENT_SCHEDULE = "event_schedule.txt" - DAY_OF_WEEK_EVENT_SCHEDULE = "day_of_week_event_schedule.txt" - DATE_EVENT_SCHEDULE = "date_event_schedule.txt" - CLOSED_EVENT_SCHEDULE = "closed_event_schedule.txt" - TRANSACTION_HISTORY = "transaction_history.txt" \ No newline at end of file diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py index ece4a45..3c1245e 100644 --- a/transactions/controllers/update_transactions_controller.py +++ b/transactions/controllers/update_transactions_controller.py @@ -1,86 +1,9 @@ -from django.contrib.auth.models import User -from django.core.paginator import Paginator -from django.db.models import Q -from datetime import datetime, timedelta, tzinfo -from random import randrange -import pytz - -from api.datatype.Eatery import EateryID - +from datetime import datetime, timedelta from transactions.models import TransactionHistory -class UpdateTransactionsController: +from util.constants import vendor_name_to_internal_id - # Converts the Vendor's name for the eatery into the name stored in our backend - @staticmethod - def vendor_name_to_internal_id(vendor_eatery_name): - vendor_eatery_name = ''.join(c.lower() for c in vendor_eatery_name if c.isalpha()) - if vendor_eatery_name == "bearnecessities": - return EateryID.BEAR_NECESSITIES - elif vendor_eatery_name == "northstarmarketplace": - return EateryID.NORTH_STAR_DINING - elif vendor_eatery_name == "jansensmarket": - return EateryID.JANSENS_MARKET - elif vendor_eatery_name == "stockinghallcafe" or vendor_eatery_name == "stockinghall": - return EateryID.DAIRY_BAR - elif vendor_eatery_name == "marthas": - return EateryID.MARTHAS_CAFE - elif vendor_eatery_name == "cafejennie": - return EateryID.CAFE_JENNIE - elif vendor_eatery_name == "goldiescafe": - return EateryID.GOLDIES_CAFE - elif vendor_eatery_name == "alicecookhouse": - return EateryID.COOK_HOUSE - elif vendor_eatery_name == "carlbeckerhouse": - return EateryID.BECKER_HOUSE - elif vendor_eatery_name == "duffield": - return EateryID.MATTINS_CAFE - elif vendor_eatery_name == "greendragon": - return EateryID.GREEN_DRAGON - elif vendor_eatery_name == "trillium": - return EateryID.TRILLIUM - elif vendor_eatery_name == "olinlibecafe": - return EateryID.LIBE_CAFE - elif vendor_eatery_name == "carolscafe": - return EateryID.CAROLS_CAFE - elif vendor_eatery_name == "statlerterrace": - return EateryID.TERRACE - elif vendor_eatery_name == "busstopbagels": - return EateryID.BUS_STOP_BAGELS - elif vendor_eatery_name == "kosher": - return EateryID.ONE_ZERO_FOUR_WEST - elif vendor_eatery_name == "jansensatbethehouse": - return EateryID.BETHE_HOUSE - elif vendor_eatery_name == "keetonhouse": - return EateryID.KEETON_HOUSE - elif vendor_eatery_name == "rpme": - return EateryID.RPCC - elif vendor_eatery_name == "rosehouse": - return EateryID.ROSE_HOUSE - elif vendor_eatery_name == "risley": - return EateryID.RISLEY - elif vendor_eatery_name == "frannysft": - return EateryID.FRANNYS - elif vendor_eatery_name == "mccormicks": - return EateryID.MCCORMICKS - elif vendor_eatery_name == "sage": - return EateryID.ATRIUM_CAFE - elif vendor_eatery_name == "straightmarket": - return EateryID.STRAIGHT_FROM_THE_MARKET - elif vendor_eatery_name == "crossingscafe": - return EateryID.CROSSINGS_CAFE - elif vendor_eatery_name == "okenshields": - return EateryID.OKENSHIELDS - elif vendor_eatery_name == "bigredbarn": - return EateryID.BIG_RED_BARN - elif vendor_eatery_name == "rustys": - return EateryID.RUSTYS - elif vendor_eatery_name == "manncafe": - return EateryID.MANN_CAFE - elif vendor_eatery_name == "statlermacs": - return EateryID.MACS_CAFE - else: - # TODO: Add a slack notif / flag that a wait time location was not recognized - return None +import pytz +class UpdateTransactionsController: def __init__(self, data): self._data = data @@ -98,7 +21,7 @@ def process(self): num_inserted = 0 ignored_names = set() for place in self._data["UNITS"]: - internal_id = UpdateTransactionsController.vendor_name_to_internal_id(place["UNIT_NAME"]).value + internal_id = vendor_name_to_internal_id(place["UNIT_NAME"]).value if internal_id == None: ignored_names.add(place["UNIT_NAME"]) else: diff --git a/util/constants.py b/util/constants.py new file mode 100644 index 0000000..b9108be --- /dev/null +++ b/util/constants.py @@ -0,0 +1,158 @@ +from enum import Enum + +from api.datatype.Eatery import EateryID + +CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" + +class SnapshotFileName(Enum): + EATERY_STORE = "eatery_store.txt" + ALERT_STORE = "alert_store.txt" + CATEGORY_STORE = "category_store.txt" + MENU_STORE = "menu_store.txt" + ITEM_STORE = "item_store.txt" + SUBITEM_STORE = "subitem_store.txt" + CATEGORY_ITEM_ASSOCIATION = "category_item_association.txt" + EVENT_SCHEDULE = "event_schedule.txt" + DAY_OF_WEEK_EVENT_SCHEDULE = "day_of_week_event_schedule.txt" + DATE_EVENT_SCHEDULE = "date_event_schedule.txt" + CLOSED_EVENT_SCHEDULE = "closed_event_schedule.txt" + TRANSACTION_HISTORY = "transaction_history.txt" + +def dining_id_to_internal_id(id: int): + if id == 31: + return EateryID.ONE_ZERO_FOUR_WEST + elif id == 7: + return EateryID.LIBE_CAFE + elif id == 8: + return EateryID.ATRIUM_CAFE + elif id == 1: + return EateryID.BEAR_NECESSITIES + elif id == 25: + return EateryID.BECKER_HOUSE + elif id == 10: + return EateryID.BIG_RED_BARN + elif id == 11: + return EateryID.BUS_STOP_BAGELS + elif id == 12: + return EateryID.CAFE_JENNIE + elif id == 2: + return EateryID.CAROLS_CAFE + elif id == 26: + return EateryID.COOK_HOUSE + elif id == 14: + return EateryID.DAIRY_BAR + elif id == 41: + return EateryID.CROSSINGS_CAFE + elif id == 32: + return EateryID.FRANNYS + elif id == 16: + return EateryID.GOLDIES_CAFE + elif id == 15: + return EateryID.GREEN_DRAGON + elif id == 24: + return EateryID.HOT_DOG_CART + elif id == 34: + return EateryID.ICE_CREAM_BIKE + elif id == 27: + return EateryID.BETHE_HOUSE + elif id == 28: + return EateryID.JANSENS_MARKET + elif id == 29: + return EateryID.KEETON_HOUSE + elif id == 42: + return EateryID.MANN_CAFE + elif id == 18: + return EateryID.MARTHAS_CAFE + elif id == 19: + return EateryID.MATTINS_CAFE + elif id == 33: + return EateryID.MCCORMICKS + elif id == 3: + return EateryID.NORTH_STAR_DINING + elif id == 20: + return EateryID.OKENSHIELDS + elif id == 4: + return EateryID.RISLEY + elif id == 5: + return EateryID.RPCC + elif id == 30: + return EateryID.ROSE_HOUSE + elif id == 21: + return EateryID.RUSTYS + elif id == 13: + return EateryID.STRAIGHT_FROM_THE_MARKET + elif id == 23: + return EateryID.TRILLIUM + else: + return None + +# Our transactions vendor +def vendor_name_to_internal_id(vendor_eatery_name): + vendor_eatery_name = ''.join(c.lower() for c in vendor_eatery_name if c.isalpha()) + if vendor_eatery_name == "bearnecessities": + return EateryID.BEAR_NECESSITIES + elif vendor_eatery_name == "northstarmarketplace": + return EateryID.NORTH_STAR_DINING + elif vendor_eatery_name == "jansensmarket": + return EateryID.JANSENS_MARKET + elif vendor_eatery_name == "stockinghallcafe" or vendor_eatery_name == "stockinghall": + return EateryID.DAIRY_BAR + elif vendor_eatery_name == "marthas": + return EateryID.MARTHAS_CAFE + elif vendor_eatery_name == "cafejennie": + return EateryID.CAFE_JENNIE + elif vendor_eatery_name == "goldiescafe": + return EateryID.GOLDIES_CAFE + elif vendor_eatery_name == "alicecookhouse": + return EateryID.COOK_HOUSE + elif vendor_eatery_name == "carlbeckerhouse": + return EateryID.BECKER_HOUSE + elif vendor_eatery_name == "duffield": + return EateryID.MATTINS_CAFE + elif vendor_eatery_name == "greendragon": + return EateryID.GREEN_DRAGON + elif vendor_eatery_name == "trillium": + return EateryID.TRILLIUM + elif vendor_eatery_name == "olinlibecafe": + return EateryID.LIBE_CAFE + elif vendor_eatery_name == "carolscafe": + return EateryID.CAROLS_CAFE + elif vendor_eatery_name == "statlerterrace": + return EateryID.TERRACE + elif vendor_eatery_name == "busstopbagels": + return EateryID.BUS_STOP_BAGELS + elif vendor_eatery_name == "kosher": + return EateryID.ONE_ZERO_FOUR_WEST + elif vendor_eatery_name == "jansensatbethehouse": + return EateryID.BETHE_HOUSE + elif vendor_eatery_name == "keetonhouse": + return EateryID.KEETON_HOUSE + elif vendor_eatery_name == "rpme": + return EateryID.RPCC + elif vendor_eatery_name == "rosehouse": + return EateryID.ROSE_HOUSE + elif vendor_eatery_name == "risley": + return EateryID.RISLEY + elif vendor_eatery_name == "frannysft": + return EateryID.FRANNYS + elif vendor_eatery_name == "mccormicks": + return EateryID.MCCORMICKS + elif vendor_eatery_name == "sage": + return EateryID.ATRIUM_CAFE + elif vendor_eatery_name == "straightmarket": + return EateryID.STRAIGHT_FROM_THE_MARKET + elif vendor_eatery_name == "crossingscafe": + return EateryID.CROSSINGS_CAFE + elif vendor_eatery_name == "okenshields": + return EateryID.OKENSHIELDS + elif vendor_eatery_name == "bigredbarn": + return EateryID.BIG_RED_BARN + elif vendor_eatery_name == "rustys": + return EateryID.RUSTYS + elif vendor_eatery_name == "manncafe": + return EateryID.MANN_CAFE + elif vendor_eatery_name == "statlermacs": + return EateryID.MACS_CAFE + else: + # TODO: Add a slack notif / flag that a wait time location was not recognized + return None \ No newline at end of file diff --git a/util/time.py b/util/time.py new file mode 100644 index 0000000..7fce11d --- /dev/null +++ b/util/time.py @@ -0,0 +1,5 @@ +from datetime import date, time, datetime +import pytz + +def combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: + return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) From 04df6cc086d97076688b0ccba5681475c166958f Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Tue, 11 Jan 2022 08:57:21 -0500 Subject: [PATCH 044/305] Update api/dfg/schedule/ClosedSchedule.py Co-authored-by: William Ma --- api/dfg/schedule/ClosedSchedule.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/dfg/schedule/ClosedSchedule.py b/api/dfg/schedule/ClosedSchedule.py index f435182..31d4fc2 100644 --- a/api/dfg/schedule/ClosedSchedule.py +++ b/api/dfg/schedule/ClosedSchedule.py @@ -1,6 +1,5 @@ from api.dfg.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID -# from eateries.models import ClosedEventSchedule class ClosedSchedule(DfgNode): From 95e830c08cccfd7e273417f688d08ab9b79ae655 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Tue, 11 Jan 2022 09:07:15 -0500 Subject: [PATCH 045/305] Move code under the src folder --- src/api/__init__.py | 0 src/api/admin.py | 3 + src/api/apps.py | 6 + src/api/datatype/Eatery.py | 143 +++++ src/api/datatype/EateryAlert.py | 30 + src/api/datatype/Event.py | 73 +++ src/api/datatype/Menu.py | 15 + src/api/datatype/MenuCategory.py | 21 + src/api/datatype/MenuItem.py | 46 ++ src/api/datatype/MenuItemSection.py | 20 + src/api/datatype/MenuSubItem.py | 28 + src/api/datatype/WaitTime.py | 29 + src/api/datatype/WaitTimesDay.py | 25 + src/api/dfg/CornellDiningNow.py | 59 ++ src/api/dfg/DfgNode.py | 10 + src/api/dfg/EateriesFromDB.py | 80 +++ src/api/dfg/EateryStubs.py | 11 + src/api/dfg/__init__.py | 0 src/api/dfg/macros/EateryEvents.py | 39 ++ src/api/dfg/macros/LeftMergeEateries.py | 31 + src/api/dfg/macros/LeftMergeEvents.py | 35 + src/api/dfg/schedule/CacheMenuInjection.py | 87 +++ src/api/dfg/schedule/ClosedSchedule.py | 19 + src/api/dfg/schedule/CornellDiningEvents.py | 113 ++++ src/api/dfg/schedule/DateSchedule.py | 16 + src/api/dfg/schedule/DayOfWeekSchedule.py | 37 ++ src/api/dfg/system/ConvertFromJson.py | 55 ++ src/api/dfg/system/ConvertToJson.py | 34 + src/api/dfg/system/DictResponseWrapper.py | 29 + src/api/dfg/system/EateryGenerator.py | 25 + src/api/dfg/system/InMemoryCache.py | 75 +++ src/api/dfg/system/LeftMerge.py | 54 ++ src/api/dfg/system/Mapping.py | 20 + src/api/dfg/wait_times/WaitTimeFilter.py | 38 ++ src/api/dfg/wait_times/WaitTimes.py | 115 ++++ src/api/migrations/0001_initial.py | 25 + src/api/migrations/__init__.py | 0 src/api/tests.py | 3 + src/api/urls.py | 8 + src/api/views.py | 86 +++ src/eateries/__init__.py | 0 src/eateries/admin.py | 15 + src/eateries/apps.py | 6 + .../management/commands/create_db_snapshot.py | 62 ++ .../management/commands/ingest_db_snapshot.py | 31 + src/eateries/migrations/0001_initial.py | 88 +++ .../0002_closedeventschedule_and_more.py | 27 + ...003_closedeventschedule_eatery_and_more.py | 26 + ...ayofweekeventschedule_dateeventschedule.py | 40 ++ ...ventschedule_event_description_and_more.py | 38 ++ ...temassociation_unique_together_and_more.py | 70 ++ ...name_exceptionstore_alertstore_and_more.py | 21 + src/eateries/migrations/__init__.py | 0 src/eateries/models.py | 111 ++++ src/eateries/serializers.py | 43 ++ src/eateries/tests.py | 3 + src/eateries/views.py | 3 + src/eatery_blue_backend/__init__.py | 0 src/eatery_blue_backend/asgi.py | 16 + src/eatery_blue_backend/settings.py | 127 ++++ src/eatery_blue_backend/urls.py | 22 + src/eatery_blue_backend/wsgi.py | 16 + src/manage.py | 22 + .../cornell_dining_now_eateries.json | 1 + src/static_sources/external_eateries.json | 600 ++++++++++++++++++ src/transactions/__init__.py | 0 src/transactions/admin.py | 6 + src/transactions/apps.py | 6 + .../update_transactions_controller.py | 35 + .../commands/fetch_recent_transactions.py | 26 + .../commands/ingest_log_transactions.py | 30 + src/transactions/migrations/0001_initial.py | 34 + src/transactions/migrations/__init__.py | 0 src/transactions/models.py | 13 + src/transactions/tests.py | 3 + src/transactions/views.py | 3 + src/util/constants.py | 158 +++++ src/util/time.py | 5 + 78 files changed, 3250 insertions(+) create mode 100644 src/api/__init__.py create mode 100644 src/api/admin.py create mode 100644 src/api/apps.py create mode 100644 src/api/datatype/Eatery.py create mode 100644 src/api/datatype/EateryAlert.py create mode 100644 src/api/datatype/Event.py create mode 100644 src/api/datatype/Menu.py create mode 100644 src/api/datatype/MenuCategory.py create mode 100644 src/api/datatype/MenuItem.py create mode 100644 src/api/datatype/MenuItemSection.py create mode 100644 src/api/datatype/MenuSubItem.py create mode 100644 src/api/datatype/WaitTime.py create mode 100644 src/api/datatype/WaitTimesDay.py create mode 100644 src/api/dfg/CornellDiningNow.py create mode 100644 src/api/dfg/DfgNode.py create mode 100644 src/api/dfg/EateriesFromDB.py create mode 100644 src/api/dfg/EateryStubs.py create mode 100644 src/api/dfg/__init__.py create mode 100644 src/api/dfg/macros/EateryEvents.py create mode 100644 src/api/dfg/macros/LeftMergeEateries.py create mode 100644 src/api/dfg/macros/LeftMergeEvents.py create mode 100644 src/api/dfg/schedule/CacheMenuInjection.py create mode 100644 src/api/dfg/schedule/ClosedSchedule.py create mode 100644 src/api/dfg/schedule/CornellDiningEvents.py create mode 100644 src/api/dfg/schedule/DateSchedule.py create mode 100644 src/api/dfg/schedule/DayOfWeekSchedule.py create mode 100644 src/api/dfg/system/ConvertFromJson.py create mode 100644 src/api/dfg/system/ConvertToJson.py create mode 100644 src/api/dfg/system/DictResponseWrapper.py create mode 100644 src/api/dfg/system/EateryGenerator.py create mode 100644 src/api/dfg/system/InMemoryCache.py create mode 100644 src/api/dfg/system/LeftMerge.py create mode 100644 src/api/dfg/system/Mapping.py create mode 100644 src/api/dfg/wait_times/WaitTimeFilter.py create mode 100644 src/api/dfg/wait_times/WaitTimes.py create mode 100644 src/api/migrations/0001_initial.py create mode 100644 src/api/migrations/__init__.py create mode 100644 src/api/tests.py create mode 100644 src/api/urls.py create mode 100644 src/api/views.py create mode 100644 src/eateries/__init__.py create mode 100644 src/eateries/admin.py create mode 100644 src/eateries/apps.py create mode 100644 src/eateries/management/commands/create_db_snapshot.py create mode 100644 src/eateries/management/commands/ingest_db_snapshot.py create mode 100644 src/eateries/migrations/0001_initial.py create mode 100644 src/eateries/migrations/0002_closedeventschedule_and_more.py create mode 100644 src/eateries/migrations/0003_closedeventschedule_eatery_and_more.py create mode 100644 src/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py create mode 100644 src/eateries/migrations/0005_dateeventschedule_event_description_and_more.py create mode 100644 src/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py create mode 100644 src/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py create mode 100644 src/eateries/migrations/__init__.py create mode 100644 src/eateries/models.py create mode 100644 src/eateries/serializers.py create mode 100644 src/eateries/tests.py create mode 100644 src/eateries/views.py create mode 100644 src/eatery_blue_backend/__init__.py create mode 100644 src/eatery_blue_backend/asgi.py create mode 100644 src/eatery_blue_backend/settings.py create mode 100644 src/eatery_blue_backend/urls.py create mode 100644 src/eatery_blue_backend/wsgi.py create mode 100755 src/manage.py create mode 100644 src/static_sources/cornell_dining_now_eateries.json create mode 100644 src/static_sources/external_eateries.json create mode 100644 src/transactions/__init__.py create mode 100644 src/transactions/admin.py create mode 100644 src/transactions/apps.py create mode 100644 src/transactions/controllers/update_transactions_controller.py create mode 100644 src/transactions/management/commands/fetch_recent_transactions.py create mode 100644 src/transactions/management/commands/ingest_log_transactions.py create mode 100644 src/transactions/migrations/0001_initial.py create mode 100644 src/transactions/migrations/__init__.py create mode 100644 src/transactions/models.py create mode 100644 src/transactions/tests.py create mode 100644 src/transactions/views.py create mode 100644 src/util/constants.py create mode 100644 src/util/time.py diff --git a/src/api/__init__.py b/src/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/admin.py b/src/api/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/src/api/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/api/apps.py b/src/api/apps.py new file mode 100644 index 0000000..878e7d5 --- /dev/null +++ b/src/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "api" diff --git a/src/api/datatype/Eatery.py b/src/api/datatype/Eatery.py new file mode 100644 index 0000000..767987e --- /dev/null +++ b/src/api/datatype/Eatery.py @@ -0,0 +1,143 @@ +from datetime import date +from typing import Optional +from enum import Enum + +import pytz +from api.datatype.EateryAlert import EateryAlert + +from api.datatype.Event import Event, filter_range +from api.datatype.WaitTimesDay import WaitTimesDay + + +class EateryID(Enum): + ONE_ZERO_FOUR_WEST = 1 + LIBE_CAFE = 2 + ATRIUM_CAFE = 3 + BEAR_NECESSITIES = 4 + BECKER_HOUSE = 5 + BIG_RED_BARN = 6 + BUS_STOP_BAGELS = 7 + CAFE_JENNIE = 8 + CAROLS_CAFE = 9 + COOK_HOUSE = 10 + DAIRY_BAR = 11 + CROSSINGS_CAFE = 12 + FRANNYS = 13 + GOLDIES_CAFE = 14 + GREEN_DRAGON = 15 + HOT_DOG_CART = 16 + ICE_CREAM_BIKE = 17 + BETHE_HOUSE = 18 + JANSENS_MARKET = 19 + KEETON_HOUSE = 20 + MANN_CAFE = 21 + MARTHAS_CAFE = 22 + MATTINS_CAFE = 23 + MCCORMICKS = 24 + NORTH_STAR_DINING = 25 + OKENSHIELDS = 26 + RISLEY = 27 + RPCC = 28 + ROSE_HOUSE = 29 + RUSTYS = 30 + STRAIGHT_FROM_THE_MARKET = 31 + TRILLIUM = 32 + TERRACE = 33 + MACS_CAFE = 34 + TEMPLE_OF_ZEUS = 35 + GIMME_COFFEE = 36 + LOUIES = 37 + ANABELS_GROCERY = 38 + +class Eatery: + + def __init__( + self, + id: EateryID, + name: Optional[str] = None, + image_url: Optional[str] = None, + menu_summary: Optional[str] = None, + campus_area: Optional[str] = None, + events: Optional[list[Event]] = None, + latitude: Optional[float] = None, + longitude: Optional[float] = None, + payment_methods: Optional[list[str]] = None, + location: Optional[str] = None, + online_order: Optional[bool] = None, + online_order_url: Optional[str] = None, + wait_times: Optional[list[WaitTimesDay]] = None, + alerts: Optional[list[EateryAlert]] = None + ): + self.id = id + self.name = name + self.image_url = image_url + self.menu_summary = menu_summary + self.campus_area = campus_area + self.latitude = latitude + self.longitude = longitude + self.known_events = events + self.payment_methods = payment_methods + self.location = location + self.online_order = online_order + self.online_order_url = online_order_url + self.wait_times = wait_times + self.alerts = alerts + + def events( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None, + ): + return filter_range(self.known_events, tzinfo, start, end) + + def to_json( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None + ): + eatery = { + "id": self.id.value, + "name": self.name, + "image_url": self.image_url, + "menu_summary": self.menu_summary, + "campus_area": self.campus_area, + "events": None if self.known_events is None + else [event.to_json() for event in self.events(tzinfo, start, end)], + "latitude": self.latitude, + "longitude": self.longitude, + "payment_methods": None if self.payment_methods is None + else [payment_method for payment_method in self.payment_methods], + "location": self.location, + "online_order": self.online_order, + "online_order_url": self.online_order_url, + "wait_times": None if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times], + "alerts": None if self.alerts is None else [alert.to_json() for alert in self.alerts] + } + return eatery + + @staticmethod + def from_json(eatery_json): + return Eatery( + id=None if "id" not in eatery_json else EateryID(eatery_json["id"]), + name=eatery_json.get("name"), + image_url=eatery_json.get("image_url"), + menu_summary=eatery_json.get("menu_summary"), + campus_area=eatery_json.get("campus_area"), + events=None if "events" not in eatery_json or eatery_json["events"] is None + else [Event.from_json(event) for event in eatery_json["events"]], + latitude=eatery_json.get("latitude"), + longitude=eatery_json.get("longitude"), + payment_methods=eatery_json.get("payment_methods"), + location=eatery_json.get("location"), + online_order=eatery_json.get("online_order"), + online_order_url=eatery_json.get("online_order_url"), + wait_times=None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None + else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]], + alerts=None if "alerts" not in eatery_json or eatery_json["alerts"] is None + else [EateryAlert.from_json(alert) for alert in eatery_json["alerts"]] + ) + + def clone(self): + return Eatery.from_json(self.to_json()) diff --git a/src/api/datatype/EateryAlert.py b/src/api/datatype/EateryAlert.py new file mode 100644 index 0000000..2bb511b --- /dev/null +++ b/src/api/datatype/EateryAlert.py @@ -0,0 +1,30 @@ +class EateryAlert: + + def __init__( + self, + id: int, + description: str, + start_timestamp: int, + end_timestamp: int + ): + self.id = id + self.description = description + self.start_timestamp = start_timestamp + self.end_timestamp = end_timestamp + + def to_json(self): + return { + "id": 1, + "description": self.description, + "start_timestamp": self.start_timestamp, + "end_timestamp": self.end_timestamp + } + + @staticmethod + def from_json(alert_json): + return EateryAlert( + id = alert_json["id"], + description=alert_json["description"], + start_timestamp=alert_json["start_timestamp"], + end_timestamp=alert_json["end_timestamp"] + ) diff --git a/src/api/datatype/Event.py b/src/api/datatype/Event.py new file mode 100644 index 0000000..9d3afcf --- /dev/null +++ b/src/api/datatype/Event.py @@ -0,0 +1,73 @@ +from typing import Optional +from datetime import date, time +from api.datatype.Menu import Menu +from util.time import combined_timestamp + +import pytz + + +class Event: + + def __init__( + self, + description: str, + canonical_date: date, + start_timestamp: int, + end_timestamp: int, + menu: Menu + ): + self.description = description + self.canonical_date = canonical_date + self.start_timestamp = start_timestamp + self.end_timestamp = end_timestamp + self.menu = menu + + def to_json( + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None + ): + return { + "description": self.description, + "canonical_date": str(self.canonical_date), + "start_timestamp": self.start_timestamp, + "end_timestamp": self.end_timestamp, + "menu": self.menu.to_json() + } + + @staticmethod + def from_json(event_json): + return Event( + description=event_json["description"], + canonical_date=date.fromisoformat(event_json["canonical_date"]), + start_timestamp=event_json["start_timestamp"], + end_timestamp=event_json["end_timestamp"], + menu=Menu.from_json(event_json["menu"]) + ) + + def __contains__(self, item: int): + return self.start_timestamp <= item <= self.end_timestamp + +def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): + if events is None: + return [] + + if start is None and end is None: + return events + + elif tzinfo is not None and start is not None and end is None: + start_ts = combined_timestamp(start, time(), tzinfo) + return [event for event in events if ( + (start_ts in event) or start == event.canonical_date + )] + + elif tzinfo is not None and start is not None and end is not None: + start_ts = combined_timestamp(start, time(), tzinfo) + end_ts = combined_timestamp(end, time(), tzinfo) + return [event for event in events if ( + (start_ts in event) or (end_ts in event) or start <= event.canonical_date <= end + )] + + else: + raise Exception(f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}") \ No newline at end of file diff --git a/src/api/datatype/Menu.py b/src/api/datatype/Menu.py new file mode 100644 index 0000000..c48e100 --- /dev/null +++ b/src/api/datatype/Menu.py @@ -0,0 +1,15 @@ +from api.datatype.MenuCategory import MenuCategory + +class Menu: + + def __init__(self, categories: list[MenuCategory]): + self.categories = categories + + def to_json(self): + return [category.to_json() for category in self.categories] + + @staticmethod + def from_json(menu_json): + return Menu( + categories=[MenuCategory.from_json(category) for category in menu_json] + ) diff --git a/src/api/datatype/MenuCategory.py b/src/api/datatype/MenuCategory.py new file mode 100644 index 0000000..f2cd1cc --- /dev/null +++ b/src/api/datatype/MenuCategory.py @@ -0,0 +1,21 @@ +from api.datatype.MenuItem import MenuItem + + +class MenuCategory: + + def __init__(self, category: str, items: list[MenuItem]): + self.category = category + self.items = items + + def to_json(self): + return { + "category": self.category, + "items": [item.to_json() for item in self.items] + } + + @staticmethod + def from_json(category_json): + return MenuCategory( + category=category_json["category"], + items=[MenuItem.from_json(item) for item in category_json["items"]] + ) diff --git a/src/api/datatype/MenuItem.py b/src/api/datatype/MenuItem.py new file mode 100644 index 0000000..b611652 --- /dev/null +++ b/src/api/datatype/MenuItem.py @@ -0,0 +1,46 @@ +from typing import Optional + +from api.datatype.MenuItemSection import MenuItemSection + +class MenuItem: + + @staticmethod + def from_cornell_dining_json(json_item: dict): + return MenuItem( + healthy=json_item["healthy"], + name=json_item["item"] + ) + + def __init__( + self, + name: str, + healthy: Optional[bool] = None, + base_price: Optional[float] = None, + description: Optional[str] = None, + sections: Optional[MenuItemSection] = None + ): + self.healthy = healthy + self.name = name + self.base_price = base_price + self.description = description + self.sections = sections + + def to_json(self): + return { + "healthy": self.healthy, + "name": self.name, + "base_price": self.base_price, + "description": self.description, + "sections": None if self.sections is None else [section.to_json() for section in self.sections] + } + + @staticmethod + def from_json(item_json): + return MenuItem( + name=item_json["name"], + healthy=item_json["healthy"], + base_price=item_json["base_price"], + description=item_json["description"], + sections=None if "sections" not in item_json or item_json["sections"] is None + else [MenuItemSection.from_json(section) for section in item_json["sections"]] + ) diff --git a/src/api/datatype/MenuItemSection.py b/src/api/datatype/MenuItemSection.py new file mode 100644 index 0000000..770c376 --- /dev/null +++ b/src/api/datatype/MenuItemSection.py @@ -0,0 +1,20 @@ +from api.datatype.MenuSubItem import MenuSubItem + +class MenuItemSection: + + def __init__(self, name: str, subitems: list[MenuSubItem]): + self.name = name + self.subitems = subitems + + def to_json(self): + return { + "name": self.name, + "subitems": [item.to_json() for item in self.subitems] + } + + @staticmethod + def from_json(section_json): + return MenuItemSection( + name=section_json["name"], + subitems=[MenuSubItem.from_json(item) for item in section_json["subitems"]] + ) diff --git a/src/api/datatype/MenuSubItem.py b/src/api/datatype/MenuSubItem.py new file mode 100644 index 0000000..854b0ed --- /dev/null +++ b/src/api/datatype/MenuSubItem.py @@ -0,0 +1,28 @@ +from typing import Optional + +class MenuSubItem: + + def __init__( + self, + name: str, + total_price: Optional[float], + additional_price: Optional[float] + ): + self.name = name + self.total_price = total_price + self.additional_price = additional_price + + def to_json(self): + return { + "name": self.name, + "total_price": self.total_price, + "additional_price": self.additional_price + } + + @staticmethod + def from_json(item_json): + return MenuSubItem( + name=item_json["name"], + total_price=item_json.get("total_price"), + additional_price=item_json.get("additional_price") + ) diff --git a/src/api/datatype/WaitTime.py b/src/api/datatype/WaitTime.py new file mode 100644 index 0000000..7e5cfca --- /dev/null +++ b/src/api/datatype/WaitTime.py @@ -0,0 +1,29 @@ +class WaitTime: + def __init__( + self, + timestamp: int, + wait_time_low: float, + wait_time_expected: float, + wait_time_high: float + ): + self.timestamp = timestamp + self.wait_time_low = wait_time_low + self.wait_time_expected = wait_time_expected + self.wait_time_high = wait_time_high + + def to_json(self): + return { + "timestamp": self.timestamp, + "wait_time_low": self.wait_time_low, + "wait_time_expected": self.wait_time_expected, + "wait_time_high": self.wait_time_high + } + + @staticmethod + def from_json(wait_time_json): + return WaitTime( + timestamp=wait_time_json["timestamp"], + wait_time_low=wait_time_json["wait_time_low"], + wait_time_expected=wait_time_json["wait_time_expected"], + wait_time_high=wait_time_json["wait_time_high"] + ) diff --git a/src/api/datatype/WaitTimesDay.py b/src/api/datatype/WaitTimesDay.py new file mode 100644 index 0000000..cb9ea86 --- /dev/null +++ b/src/api/datatype/WaitTimesDay.py @@ -0,0 +1,25 @@ +from datetime import date +from .WaitTime import WaitTime + + +class WaitTimesDay: + def __init__( + self, + canonical_date: date, + data: list[WaitTime] + ): + self.canonical_date = canonical_date + self.data = data + + def to_json(self): + return { + "canonical_date": str(self.canonical_date), + "data": [wait_time.to_json() for wait_time in self.data] + } + + @staticmethod + def from_json(wait_times_day_json): + return WaitTimesDay( + canonical_date=date.fromisoformat(wait_times_day_json["canonical_date"]), + data=[WaitTime.from_json(wait_time) for wait_time in wait_times_day_json["data"]] + ) diff --git a/src/api/dfg/CornellDiningNow.py b/src/api/dfg/CornellDiningNow.py new file mode 100644 index 0000000..de104d7 --- /dev/null +++ b/src/api/dfg/CornellDiningNow.py @@ -0,0 +1,59 @@ +import requests + +from api.dfg.DfgNode import DfgNode + +from api.datatype.Eatery import Eatery +from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL + +from datetime import date + +class CornellDiningNow(DfgNode): + + def __call__(self, *args, **kwargs) -> list[Eatery]: + try: + response = requests.get(CORNELL_DINING_URL).json() + + except Exception as e: + raise e + + if response["status"] == "success": + json_eateries = response["data"]["eateries"] + eateries = [] + for json_eatery in json_eateries: + eateries.append(CornellDiningNow.parse_eatery(json_eatery)) + return eateries + + else: + raise Exception(response["message"]) + + @staticmethod + def parse_eatery(json_eatery: dict) -> Eatery: + # Events are parsed later + return Eatery( + id=dining_id_to_internal_id(json_eatery["id"]), + name=json_eatery["name"], + campus_area=json_eatery["campusArea"]["descrshort"], + latitude=json_eatery["latitude"], + longitude=json_eatery["longitude"], + payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), + location=json_eatery["location"], + online_order=json_eatery["onlineOrdering"], + online_order_url=json_eatery["onlineOrderUrl"] + ) + + @staticmethod + def generate_payment_methods(json_paymethods: list): + payment_methods = [] + takes_cash = True + takes_brbs = any([method["descrshort"] == "Meal Plan - Debit" for method in json_paymethods]) + takes_swipes = any([method["descrshort"] == "Meal Plan - Swipe" for method in json_paymethods]) + if takes_cash: + payment_methods.append("cash") + if takes_brbs: + payment_methods.append("brbs") + if takes_swipes: + payment_methods.append("swipes") + return payment_methods + + def description(self): + return "CornellDiningNow" diff --git a/src/api/dfg/DfgNode.py b/src/api/dfg/DfgNode.py new file mode 100644 index 0000000..57d5821 --- /dev/null +++ b/src/api/dfg/DfgNode.py @@ -0,0 +1,10 @@ +class DfgNode: + + def __call__(self, *args, **kwargs): + raise Exception() + + def children(self): + return [] + + def description(self): + raise Exception() diff --git a/src/api/dfg/EateriesFromDB.py b/src/api/dfg/EateriesFromDB.py new file mode 100644 index 0000000..d474e66 --- /dev/null +++ b/src/api/dfg/EateriesFromDB.py @@ -0,0 +1,80 @@ +import datetime +import json +import re + +import pytz + +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.EateryAlert import EateryAlert +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem +from api.dfg.DfgNode import DfgNode + +from eateries.models import EateryStore, AlertStore +from eateries.serializers import EateryStoreSerializer, AlertStoreSerializer + +from datetime import datetime + +# eventually need to deprecate this for a custom DB backend storing all of the overrides + +class EateriesFromDB(DfgNode): + + def __call__(self, *args, **kwargs) -> list[Eatery]: + eateries = EateryStore.objects.all() + serialized_eateries = EateryStoreSerializer(data=eateries, many=True) + serialized_eateries.is_valid() + alerts = AlertStore.objects.filter(end_timestamp__gte=datetime.now().timestamp(), start_timestamp__lte=datetime.now().timestamp()) + serialized_alerts = AlertStoreSerializer(data=alerts, many=True) + serialized_alerts.is_valid() + return list(map(lambda x: EateriesFromDB.eatery_from_serialized(x, serialized_alerts.data), serialized_eateries.data)) + + @staticmethod + def none_repr(str): + return None if str == None or len(str) == 0 else str + + @staticmethod + def eatery_from_serialized(serialized_eatery: dict, serialized_alerts: list[dict]) -> Eatery: + return Eatery( + id=EateryID(serialized_eatery["id"]), + name=EateriesFromDB.none_repr(serialized_eatery["name"]), + image_url=EateriesFromDB.none_repr(serialized_eatery["image_url"]), + menu_summary=EateriesFromDB.none_repr(serialized_eatery["menu_summary"]), + campus_area=EateriesFromDB.none_repr(serialized_eatery["campus_area"]), + events=None, + latitude=serialized_eatery["latitude"], + longitude=serialized_eatery["longitude"], + payment_methods=EateriesFromDB.payment_methods(serialized_eatery), + location=EateriesFromDB.none_repr(serialized_eatery["location"]), + online_order=serialized_eatery["online_order"], + online_order_url=EateriesFromDB.none_repr(serialized_eatery["online_order_url"]), + alerts = EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts) + ) + + @staticmethod + def payment_methods(serialized_eatery:dict): + pay_methods = [] + if serialized_eatery["payment_accepts_cash"]: + pay_methods.append("cash") + if serialized_eatery["payment_accepts_brbs"]: + pay_methods.append("brbs") + if serialized_eatery["payment_accepts_meal_swipes"]: + pay_methods.append("swipes") + return pay_methods + + @staticmethod + def alerts(eatery_id: int, serialized_alerts: list[dict]): + return [EateriesFromDB.alert_from_serialized(alert) for alert in serialized_alerts if alert["eatery"] == eatery_id] + + @staticmethod + def alert_from_serialized(serialized_alert: dict): + return EateryAlert( + id=serialized_alert["id"], + description=serialized_alert["description"], + start_timestamp=serialized_alert["start_timestamp"], + end_timestamp=serialized_alert["end_timestamp"] + ) + + + diff --git a/src/api/dfg/EateryStubs.py b/src/api/dfg/EateryStubs.py new file mode 100644 index 0000000..4b1d4c6 --- /dev/null +++ b/src/api/dfg/EateryStubs.py @@ -0,0 +1,11 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID + + +class EateryStubs(DfgNode): + + def __call__(self, *args, **kwargs) -> list[Eatery]: + return [Eatery(id=id) for id in EateryID] + + def description(self): + return "EateryStubs" diff --git a/src/api/dfg/__init__.py b/src/api/dfg/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/dfg/macros/EateryEvents.py b/src/api/dfg/macros/EateryEvents.py new file mode 100644 index 0000000..26c7079 --- /dev/null +++ b/src/api/dfg/macros/EateryEvents.py @@ -0,0 +1,39 @@ +from api.dfg.DfgNode import DfgNode + +from api.dfg.schedule.ClosedSchedule import ClosedSchedule +from api.dfg.schedule.DayOfWeekSchedule import DayOfWeekSchedule +from api.dfg.schedule.DateSchedule import DateSchedule +from api.dfg.schedule.CornellDiningEvents import CornellDiningEvents + +from api.dfg.macros.LeftMergeEvents import LeftMergeEvents + +from api.datatype.Eatery import EateryID +from api.dfg.schedule.CacheMenuInjection import CacheMenuInjection + +# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if +# conflict) +class EateryEvents(DfgNode): + def __init__(self, eatery_id: EateryID, cache): + self.macro = CacheMenuInjection( + ClosedSchedule( + eatery_id, + LeftMergeEvents( + DateSchedule(eatery_id, cache), + LeftMergeEvents( + DayOfWeekSchedule(eatery_id, cache), + CornellDiningEvents(eatery_id, cache) + ) + ), + cache + ), + cache + ) + + def children(self): + return self.macro.children() + + def __call__(self, *args, **kwargs): + return self.macro(*args, **kwargs) + + def description(self): + return "LeftMergeEvents" \ No newline at end of file diff --git a/src/api/dfg/macros/LeftMergeEateries.py b/src/api/dfg/macros/LeftMergeEateries.py new file mode 100644 index 0000000..92f5e87 --- /dev/null +++ b/src/api/dfg/macros/LeftMergeEateries.py @@ -0,0 +1,31 @@ +from api.dfg.DfgNode import DfgNode +from api.dfg.system.ConvertToJson import ConvertToJson +from api.dfg.system.ConvertFromJson import EateryFromJson +from api.dfg.system.LeftMerge import LeftMerge + +class LeftMergeEateries(DfgNode): + + def __init__(self, left: DfgNode, right: DfgNode): + def comparator(left, right): + if left["id"] == right["id"]: + return 0 + elif left["id"] < right["id"]: + return -1 + else: + return 1 + self.macro = EateryFromJson( + LeftMerge( + ConvertToJson(left), + ConvertToJson(right), + comparator + ) + ) + + def children(self): + return self.macro.children() + + def __call__(self, *args, **kwargs): + return self.macro(*args, **kwargs) + + def description(self): + return "LeftMergeEateries" diff --git a/src/api/dfg/macros/LeftMergeEvents.py b/src/api/dfg/macros/LeftMergeEvents.py new file mode 100644 index 0000000..264f940 --- /dev/null +++ b/src/api/dfg/macros/LeftMergeEvents.py @@ -0,0 +1,35 @@ +from api.dfg.DfgNode import DfgNode + +from api.dfg.system.ConvertToJson import ConvertToJson +from api.dfg.system.ConvertFromJson import EventFromJson +from api.dfg.system.LeftMerge import LeftMerge + +# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if +# conflict) +class LeftMergeEvents(DfgNode): + def __init__(self, left: DfgNode, right: DfgNode): + def comparator(left, right): + left_val = (left["canonical_date"], left["description"]) + right_val = (right["canonical_date"], right["description"]) + if left_val == right_val: + return 0 + elif left_val < right_val: + return -1 + else: + return 1 + self.macro = EventFromJson( + LeftMerge( + ConvertToJson(left), + ConvertToJson(right), + comparator + ) + ) + + def children(self): + return self.macro.children() + + def __call__(self, *args, **kwargs): + return self.macro(*args, **kwargs) + + def description(self): + return "LeftMergeEvents" \ No newline at end of file diff --git a/src/api/dfg/schedule/CacheMenuInjection.py b/src/api/dfg/schedule/CacheMenuInjection.py new file mode 100644 index 0000000..a339102 --- /dev/null +++ b/src/api/dfg/schedule/CacheMenuInjection.py @@ -0,0 +1,87 @@ +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem +from api.datatype.MenuItemSection import MenuItemSection +from api.datatype.MenuSubItem import MenuSubItem +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from eateries.models import CategoryItemAssociation, SubItemStore + +class CacheMenuInjection(DfgNode): + + def __init__(self, child: DfgNode, cache): + self.cache = cache + self.child = child + + def __call__(self, *args, **kwargs) -> list[Eatery]: + if "menus" not in self.cache: + associations = CategoryItemAssociation.objects \ + .select_related("item") \ + .select_related("category") \ + .select_related("category__menu") \ + .all() + subitems = SubItemStore.objects.all() + + eatery_menus_categories_map = {} + item_subitem_map = {} + for subitem in subitems: + item_id = subitem.item_id + item_subsection = subitem.item_subsection + if item_id not in item_subitem_map: + item_subitem_map[item_id] = {} + if item_subsection not in item_subitem_map[item_id]: + item_subitem_map[item_id][item_subsection] = [] + item_subitem_map[item_id][item_subsection].append( + MenuSubItem( + name=subitem.name, + additional_price=subitem.additional_price, + total_price=subitem.total_price + ) + ) + + for association in associations: + eatery_id = EateryID(association.category.menu.eatery_id) + menu_id = association.category.menu_id + category = association.category.category + if eatery_id not in eatery_menus_categories_map: + eatery_menus_categories_map[eatery_id] = {} + if menu_id not in eatery_menus_categories_map[eatery_id]: + eatery_menus_categories_map[eatery_id][menu_id] = {} + if category not in eatery_menus_categories_map[eatery_id][menu_id]: + eatery_menus_categories_map[eatery_id][menu_id][category] = [] + item_sections = None + if association.item.id in item_subitem_map: + item_sections = [] + for section in item_subitem_map[association.item.id]: + item_sections.append(MenuItemSection(section, item_subitem_map[association.item.id][section])) + eatery_menus_categories_map[eatery_id][menu_id][category].append( + MenuItem( + name = association.item.name, + healthy = None, + base_price = association.item.base_price, + description = association.item.description, + sections = item_sections + ) + ) + eatery_menus_map = {} + for eatery_id in eatery_menus_categories_map: + eatery_menus_map[eatery_id] = {} + for menu_id in eatery_menus_categories_map[eatery_id]: + categories = [] + for category in eatery_menus_categories_map[eatery_id][menu_id]: + categories.append( + MenuCategory( + category=category, + items = eatery_menus_categories_map[eatery_id][menu_id][category] + ) + ) + eatery_menus_map[eatery_id][menu_id] = Menu( + categories=categories + ) + self.cache["menus"] = eatery_menus_map + + # self.cache["menus"] is a map of form {[eatery_id]: {[menu_id]: [Menu]}} + return self.child(*args, **kwargs) + + def description(self): + return "EateryStubs" diff --git a/src/api/dfg/schedule/ClosedSchedule.py b/src/api/dfg/schedule/ClosedSchedule.py new file mode 100644 index 0000000..31d4fc2 --- /dev/null +++ b/src/api/dfg/schedule/ClosedSchedule.py @@ -0,0 +1,19 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID + +class ClosedSchedule(DfgNode): + + def __init__(self, eatery_id: EateryID, child: DfgNode, cache): + self.eatery_id = eatery_id + self.child = child + self.cache = cache + + def __call__(self, *args, **kwargs) -> list[Eatery]: + # ClosedEventSchedule.objects.all() + return self.child(*args, **kwargs) + + def children(self): + return [self.child] + + def description(self): + return "ClosedSchedule" diff --git a/src/api/dfg/schedule/CornellDiningEvents.py b/src/api/dfg/schedule/CornellDiningEvents.py new file mode 100644 index 0000000..9bfc206 --- /dev/null +++ b/src/api/dfg/schedule/CornellDiningEvents.py @@ -0,0 +1,113 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem +from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL + +from datetime import date +import requests + +class CornellDiningEvents(DfgNode): + + def __init__(self, eatery_id: EateryID, cache): + self.eatery_id = eatery_id + self.cache = cache + + def __call__(self, *args, **kwargs) -> list[Eatery]: + if "eateries" not in self.cache: + try: + response = requests.get(CORNELL_DINING_URL).json() + except Exception as e: + raise e + if response["status"] == "success": + json_eateries = response["data"]["eateries"] + eateries = [] + for json_eatery in json_eateries: + eateries.append(CornellDiningEvents.parse_eatery(json_eatery)) + self.cache["eateries"] = eateries + for eatery in self.cache["eateries"]: + if eatery.id == self.eatery_id: + return eatery.events() + return [] + + @staticmethod + def parse_eatery(json_eatery: dict) -> Eatery: + is_cafe = "Cafe" in { + eatery_type["descr"] + for eatery_type in json_eatery["eateryTypes"] + } + return Eatery( + id=dining_id_to_internal_id(json_eatery["id"]), + events=CornellDiningEvents.eatery_events_from_json( + json_operating_hours=json_eatery["operatingHours"], + json_dining_items=json_eatery["diningItems"], + is_cafe=is_cafe + ) + ) + @staticmethod + def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: + json_operating_hours = sorted( + json_operating_hours, + key=lambda json_date_events: json_date_events["date"] + ) + events = [] + + for json_date_events in json_operating_hours: + canonical_date = date.fromisoformat(json_date_events["date"]) + + for json_event in json_date_events["events"]: + events.append(Event( + canonical_date=canonical_date, + description=json_event["descr"], + start_timestamp=json_event["startTimestamp"], + end_timestamp=json_event["endTimestamp"], + menu=CornellDiningEvents.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe) + )) + + return events + + @staticmethod + def eatery_menu_from_json(json_menu: list, json_dining_items: list, is_cafe: bool): + if is_cafe: + return CornellDiningEvents.cafe_menu_from_json(json_dining_items) + else: + return CornellDiningEvents.dining_hall_menu_from_json(json_menu) + + @staticmethod + def cafe_menu_from_json(json_dining_items: list) -> Menu: + category_map = {} + for item in json_dining_items: + if item['category'] not in category_map: + category_map[item['category']] = [] + category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) + categories = [] + for category_name in category_map: + categories.append(MenuCategory(category_name, category_map[category_name])) + return Menu(categories=categories) + + @staticmethod + def dining_hall_menu_from_json(json_menu: list) -> Menu: + json_menu = sorted( + json_menu, + key=lambda json_menu_category: json_menu_category["sortIdx"] + ) + menu_categories = [] + + for json_menu_category in json_menu: + items = [ + MenuItem.from_cornell_dining_json(json_item) + for json_item in json_menu_category["items"] + ] + + menu_categories.append(MenuCategory( + category=json_menu_category["category"], + items=items + )) + + return Menu(categories=menu_categories) + + + def description(self): + return "CornellDiningEvents" \ No newline at end of file diff --git a/src/api/dfg/schedule/DateSchedule.py b/src/api/dfg/schedule/DateSchedule.py new file mode 100644 index 0000000..738eaa0 --- /dev/null +++ b/src/api/dfg/schedule/DateSchedule.py @@ -0,0 +1,16 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +# from eateries.models import DateEventSchedule + +class DateSchedule(DfgNode): + + def __init__(self, eatery_id: EateryID, cache): + self.eatery_id = eatery_id + self.cache = cache + + def __call__(self, *args, **kwargs) -> list[Eatery]: + # DateEventSchedule.objects.all() + return [] + + def description(self): + return "EateryStubs" diff --git a/src/api/dfg/schedule/DayOfWeekSchedule.py b/src/api/dfg/schedule/DayOfWeekSchedule.py new file mode 100644 index 0000000..2966806 --- /dev/null +++ b/src/api/dfg/schedule/DayOfWeekSchedule.py @@ -0,0 +1,37 @@ + +from api.datatype.Event import Event +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from eateries.models import DayOfWeekEventSchedule +from datetime import timedelta, datetime +from util.time import combined_timestamp + +import pytz + +class DayOfWeekSchedule(DfgNode): + + def __init__(self, eatery_id: EateryID, cache): + self.eatery_id = eatery_id + self.cache = cache + + def __call__(self, *args, **kwargs) -> list[Eatery]: + if "day_of_week_schedules" not in self.cache: + self.cache["day_of_week_schedules"] = DayOfWeekEventSchedule.objects.all().values() + schedules = [sched for sched in self.cache["day_of_week_schedules"] if EateryID(sched["eatery_id"]) == self.eatery_id] + events = [] + date = kwargs.get("start") + while date <= kwargs.get("end"): + day_schedule = [sched for sched in schedules if sched["day_of_week"] == date.strftime("%A")] + for sched in day_schedule: + events.append(Event( + description=sched["event_description"], + canonical_date=date, + start_timestamp=combined_timestamp(date=date, time=sched["start"], tzinfo=kwargs.get("tzinfo")), + end_timestamp=combined_timestamp(date=date, time=sched["end"], tzinfo=kwargs.get("tzinfo")), + menu=self.cache["menus"][self.eatery_id][sched["menu_id"]] + )) + date += timedelta(days = 1) + return events + + def description(self): + return "DayOfWeekSchedule" diff --git a/src/api/dfg/system/ConvertFromJson.py b/src/api/dfg/system/ConvertFromJson.py new file mode 100644 index 0000000..b85f28f --- /dev/null +++ b/src/api/dfg/system/ConvertFromJson.py @@ -0,0 +1,55 @@ +from typing import Union + +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery +from api.datatype.Event import Event + +class EateryFromJson(DfgNode): + + def __init__(self, child: DfgNode): + self.child = child + + def __call__(self, *args, **kwargs): + result = self.child(*args, **kwargs) + return EateryFromJson.from_json(result, *args, **kwargs) + + def children(self): + return [self.child] + + @staticmethod + def from_json(obj: Union[list, dict], *args, **kwargs): + if isinstance(obj, list): + return [ + EateryFromJson.from_json(elem, *args, **kwargs) + for elem in obj + ] + else: + return Eatery.from_json(obj) + + def description(self): + return "EateryFromJson" + +class EventFromJson(DfgNode): + + def __init__(self, child: DfgNode): + self.child = child + + def __call__(self, *args, **kwargs): + result = self.child(*args, **kwargs) + return EventFromJson.from_json(result, *args, **kwargs) + + def children(self): + return [self.child] + + @staticmethod + def from_json(obj: Union[list, dict], *args, **kwargs): + if isinstance(obj, list): + return [ + EventFromJson.from_json(elem, *args, **kwargs) + for elem in obj + ] + else: + return Event.from_json(obj) + + def description(self): + return "EventFromJson" diff --git a/src/api/dfg/system/ConvertToJson.py b/src/api/dfg/system/ConvertToJson.py new file mode 100644 index 0000000..25129f0 --- /dev/null +++ b/src/api/dfg/system/ConvertToJson.py @@ -0,0 +1,34 @@ +from typing import Union +from api.datatype.Event import Event + +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery + +class ConvertToJson(DfgNode): + + def __init__(self, child: DfgNode): + self.child = child + + def __call__(self, *args, **kwargs): + result = self.child(*args, **kwargs) + return ConvertToJson.to_json(result, *args, **kwargs) + + def children(self): + return [self.child] + + @staticmethod + def to_json(obj: Union[list, Eatery, Event], *args, **kwargs): + if isinstance(obj, list): + return [ + ConvertToJson.to_json(elem, *args, **kwargs) + for elem in obj + ] + else: + return obj.to_json( + tzinfo=kwargs.get("tzinfo"), + start=kwargs.get("start"), + end=kwargs.get("end") + ) + + def description(self): + return "ConvertToJson" diff --git a/src/api/dfg/system/DictResponseWrapper.py b/src/api/dfg/system/DictResponseWrapper.py new file mode 100644 index 0000000..618d89b --- /dev/null +++ b/src/api/dfg/system/DictResponseWrapper.py @@ -0,0 +1,29 @@ +from api.dfg.DfgNode import DfgNode + + +class DictResponseWrapper(DfgNode): + + def __init__(self, child: DfgNode, re_raise_exceptions: bool = False): + self.child = child + self.re_raise_exceptions = re_raise_exceptions + + def __call__(self, *args, **kwargs): + try: + return { + "success": True, + "data": self.child(*args, **kwargs), + "error": None + } + + except Exception as e: + if self.re_raise_exceptions: + raise e + + return { + "success": False, + "data": None, + "error": str(e) + } + + def description(self): + return "DictResponseWrapper" diff --git a/src/api/dfg/system/EateryGenerator.py b/src/api/dfg/system/EateryGenerator.py new file mode 100644 index 0000000..7cbb6dc --- /dev/null +++ b/src/api/dfg/system/EateryGenerator.py @@ -0,0 +1,25 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from typing import Optional + +class EateryGenerator(DfgNode): + + def __init__( + self, + eatery_id: EateryID, + events_dfg: Optional[DfgNode] = None, + wait_times_dfg: Optional[DfgNode] = None + ): + self.eatery_id = eatery_id + self.events_dfg = events_dfg + self.wait_times_dfg = wait_times_dfg + + def __call__(self, *args, **kwargs) -> list: + return Eatery( + id = self.eatery_id, + events=None if self.events_dfg is None else self.events_dfg(*args, **kwargs), + wait_times=None if self.wait_times_dfg is None else self.wait_times_dfg(*args, **kwargs) + ) + + def description(self): + return "EateryGenerator" diff --git a/src/api/dfg/system/InMemoryCache.py b/src/api/dfg/system/InMemoryCache.py new file mode 100644 index 0000000..afd6ea9 --- /dev/null +++ b/src/api/dfg/system/InMemoryCache.py @@ -0,0 +1,75 @@ +import time + +from api.dfg.DfgNode import DfgNode +from typing import Optional + +from api.dfg.system.ConvertToJson import ConvertToJson + + +class DataSnapshot: + + def __init__(self, args, kwargs, data, time): + self.data = data + self.recorded_time = time + self.args = args + self.kwargs = kwargs + + def is_usable_snapshot(self, oldest_possible_time, args, kwargs): + return args == self.args and kwargs == self.kwargs and self.recorded_time >= oldest_possible_time + + def get_data(self): + return self.data + + def to_json(self): + return ConvertToJson.to_json(self.data, *self.args, **self.kwargs) + + def get_recorded_time(self): + return self.recorded_time + + +class InMemoryCache(DfgNode): + + def __init__(self, child, expiration: float = 3600, max_size: int = 5): + self.child = child + self.expiration = expiration + self.max_size = max_size + self.snapshots: list[DataSnapshot] = [] + + def current_time(self): + return time.time() + + def fifo_index(self): + if len(self.snapshots) == 0: + return None + oldest_snapshot_time = self.snapshots[0].get_recorded_time() + oldest_snapshot_index = 0 + for i in range(1, len(self.snapshots)): + if self.snapshots[i].get_recorded_time() < oldest_snapshot_time: + oldest_snapshot_time = self.snapshots[i].get_recorded_time() + oldest_snapshot_index = i + return oldest_snapshot_index + + def __call__(self, *args, **kwargs): + should_reload = kwargs.get("reload") + for snapshot in self.snapshots: + if not should_reload and snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): + # print(f"{self}: Returning from cache") + return snapshot.get_data() + + # print(f"{self}: Fetching data") + new_snapshot = DataSnapshot(args, kwargs, self.child(*args, **kwargs), self.current_time()) + if len(self.snapshots) < self.max_size: + self.snapshots.append(new_snapshot) + else: + index_to_replace = self.fifo_index() + self.snapshots[index_to_replace] = new_snapshot + return new_snapshot.get_data() + + def to_json(self, *args, **kwargs): + for snapshot in self.snapshots: + if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): + return snapshot.to_json() + return ConvertToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) + + def description(self): + return "InMemoryCache" diff --git a/src/api/dfg/system/LeftMerge.py b/src/api/dfg/system/LeftMerge.py new file mode 100644 index 0000000..557daf1 --- /dev/null +++ b/src/api/dfg/system/LeftMerge.py @@ -0,0 +1,54 @@ +from api.dfg.DfgNode import DfgNode +from typing import Callable, TypeVar +from functools import cmp_to_key +T = TypeVar("T") + +# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if +# conflict) +class LeftMerge(DfgNode): + + def __init__(self, left: DfgNode, right: DfgNode, comparator: Callable[[T, T], int]): + self.left = left + self.right = right + self.comparator = comparator + + def children(self): + return [self.left, self.right] + + def __call__(self, *args, **kwargs): + left_lst = sorted(self.left(*args, **kwargs), key=cmp_to_key(self.comparator)) + right_lst = sorted(self.right(*args, **kwargs), key=cmp_to_key(self.comparator)) + left_json = _pop_first(left_lst) + right_json = _pop_first(right_lst) + merged_lst = [] + while left_json is not None and right_json is not None: + if self.comparator(left_json, right_json) == 0: + merged_json = {} + for key in right_json: + if right_json[key] is not None: + merged_json[key] = right_json[key] + for key in left_json: + if left_json[key] is not None: + merged_json[key] = left_json[key] + merged_lst.append(merged_json) + left_json = _pop_first(left_lst) + right_json = _pop_first(right_lst) + elif self.comparator(left_json, right_json) < 0: + merged_lst.append(left_json) + left_json = _pop_first(left_lst) + else: + merged_lst.append(right_json) + right_json = _pop_first(right_lst) + merged_lst.extend(left_lst) + merged_lst.extend(right_lst) + return merged_lst + + def description(self): + return "LeftMerge" + + +def _pop_first(lst: list): + try: + return lst.pop(0) + except IndexError: + return None diff --git a/src/api/dfg/system/Mapping.py b/src/api/dfg/system/Mapping.py new file mode 100644 index 0000000..95539e3 --- /dev/null +++ b/src/api/dfg/system/Mapping.py @@ -0,0 +1,20 @@ +from api.dfg.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from typing import Callable, Any + +class Mapping(DfgNode): + + def __init__(self, child: DfgNode, fn: Callable[[Any, dict], DfgNode]): + self.child = child + self.fn = fn + + def __call__(self, *args, **kwargs) -> list: + result = [] + cache = {} + for ele in self.child(*args, **kwargs): + dfg = self.fn(ele, cache) + result.append(dfg(*args, **kwargs)) + return result + + def description(self): + return "Mapping" diff --git a/src/api/dfg/wait_times/WaitTimeFilter.py b/src/api/dfg/wait_times/WaitTimeFilter.py new file mode 100644 index 0000000..ae2bac0 --- /dev/null +++ b/src/api/dfg/wait_times/WaitTimeFilter.py @@ -0,0 +1,38 @@ +from api.datatype.WaitTimesDay import WaitTimesDay +from api.dfg.DfgNode import DfgNode + + +# Removes all wait times that are not part of the eatery's events + +class WaitTimeFilter(DfgNode): + def __init__(self, child: DfgNode): + self.child = child + + def children(self): + return [self.child] + + def __call__(self, *args, **kwargs): + eateries = self.child(*args, **kwargs) + result = [] + for eatery in eateries: + if eatery.wait_times is None: + result.append(eatery.clone()) + else: + wait_times_filtered = [] + for day_wait_times in eatery.wait_times: + filtered_data = [] + for wait_time_data in day_wait_times.data: + eatery_events = eatery.events() + if any([wait_time_data.timestamp in event for event in eatery_events]): + filtered_data.append(wait_time_data) + wait_times_filtered.append(WaitTimesDay( + canonical_date=day_wait_times.canonical_date, + data=filtered_data + )) + eatery_clone = eatery.clone() + eatery_clone.wait_times = wait_times_filtered + result.append(eatery_clone) + return result + + def description(self): + return "WaitTimeFilter" diff --git a/src/api/dfg/wait_times/WaitTimes.py b/src/api/dfg/wait_times/WaitTimes.py new file mode 100644 index 0000000..369a9e6 --- /dev/null +++ b/src/api/dfg/wait_times/WaitTimes.py @@ -0,0 +1,115 @@ +from datetime import date, timedelta + +import pytz +from django.db.models import Avg + +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.Event import Event +from api.datatype.WaitTime import WaitTime +from api.datatype.WaitTimesDay import WaitTimesDay +from api.dfg.DfgNode import DfgNode +from transactions.models import TransactionHistory + +from util.time import combined_timestamp + +class WaitTimes(DfgNode): + + def __init__(self, eatery_id: EateryID, cache): + self.eatery_id = eatery_id + self.cache = cache + + def __call__(self, *args, **kwargs) -> list[Eatery]: + if "transactions" not in self.cache: + transactions = {} + date = kwargs.get("start") + while date <= kwargs.get("end"): + transactions[date] = [] + past_days = [] + for i in range(1, 13): + past_days.append(date - timedelta(days=7*i)) + transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ + .values("eatery_id", "block_end_time") \ + .annotate(transaction_avg=Avg("transaction_count")) + for unit in transaction_avg_counts: + transactions[date].append(unit) + date += timedelta(days=1) + self.cache["transactions"] = transactions + + eatery_wait_times = [] + for date in self.cache["transactions"]: + eatery_transaction_avgs = [transaction_avg for transaction_avg in self.cache["transactions"][date] if transaction_avg["eatery_id"] == self.eatery_id.value] + eatery_wait_times.append(WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs, kwargs.get("tzinfo"))) + + return eatery_wait_times + + # Expected amount of time (in seconds) for the length of the line to decrease by 1 person + # Returns [lower, expected, upper] + @staticmethod + def line_decrease_by_one_time(eatery_id: EateryID) -> list[int]: + if eatery_id == EateryID.MACS_CAFE: + return [24, 27, 30] + elif eatery_id == EateryID.MATTINS_CAFE: + return [9, 15, 21] + elif eatery_id == EateryID.TERRACE: + return [15, 27, 36] + elif eatery_id == EateryID.OKENSHIELDS: + return [4, 8, 12] + else: + return [18, 21, 24] + + # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the + # amount of time to check out Returns [lower, expected, upper] + @staticmethod + def base_time_to_get_food(eatery_id: EateryID) -> list[int]: + if eatery_id == EateryID.MACS_CAFE: + return [240, 300, 360] + elif eatery_id == EateryID.MATTINS_CAFE: + return [150, 210, 270] + elif eatery_id == EateryID.TERRACE: + return [180, 300, 420] + elif eatery_id == EateryID.OKENSHIELDS: + return [80, 120, 180] + else: + return [180, 240, 300] + + @staticmethod + def generate_eatery_wait_times_by_day( + eatery_id: EateryID, + date: date, + transactions: list, + tzinfo: pytz.tzinfo + ) -> WaitTimesDay: + wait_times_data = [] + customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + for index in reversed(range(0, len(transactions))): + base_times = WaitTimes.base_time_to_get_food(eatery_id) + line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) + # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago + how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index]["transaction_avg"] + prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) + if prev_bucket_guest_arrival > 9: + pass + # TODO: Send a slack error here instead + # print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") + else: + customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index]["transaction_avg"] + num_customers = customers_waiting_in_line.pop(0) + wait_time_low = int(base_times[0] + line_decrease_times[0] * num_customers) + wait_time_expected = int(base_times[1] + line_decrease_times[1] * num_customers) + wait_time_high = int(base_times[2] + line_decrease_times[2] * num_customers) + + customers_waiting_in_line.append(0.0) + block_end_time = transactions[index]['block_end_time'] + timestamp = int(combined_timestamp(date, block_end_time, tzinfo) - 5 * 60 / 2) + wait_times_data.insert(0, WaitTime( + timestamp=timestamp, + wait_time_low=wait_time_low, + wait_time_expected=wait_time_expected, + wait_time_high=wait_time_high + )) + + return WaitTimesDay(canonical_date=date, data=wait_times_data) + + + def description(self): + return "WaitTimes" diff --git a/src/api/migrations/0001_initial.py b/src/api/migrations/0001_initial.py new file mode 100644 index 0000000..c6f14d5 --- /dev/null +++ b/src/api/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0 on 2021-12-23 19:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='TransactionHistory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('canonical_date', models.DateField()), + ('start_timestamp', models.TimeField()), + ('end_timestamp', models.TimeField()), + ('transaction_count', models.IntegerField()), + ], + ), + ] diff --git a/src/api/migrations/__init__.py b/src/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/tests.py b/src/api/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/api/urls.py b/src/api/urls.py new file mode 100644 index 0000000..4bf0e28 --- /dev/null +++ b/src/api/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("", views.index, name="index"), + path("gs", views.google_sheets_eateries, name="gs") +] diff --git a/src/api/views.py b/src/api/views.py new file mode 100644 index 0000000..e44d9af --- /dev/null +++ b/src/api/views.py @@ -0,0 +1,86 @@ +from datetime import date, timedelta + +import pytz +from django.http import JsonResponse +from api.datatype.Eatery import Eatery + +from api.dfg.CornellDiningNow import CornellDiningNow +from api.dfg.EateryStubs import EateryStubs +from api.dfg.EateriesFromDB import EateriesFromDB +from api.dfg.macros.EateryEvents import EateryEvents + +from api.dfg.system.DictResponseWrapper import DictResponseWrapper +from api.dfg.system.ConvertToJson import ConvertToJson +from api.dfg.system.EateryGenerator import EateryGenerator +from api.dfg.system.InMemoryCache import InMemoryCache +from api.dfg.system.Mapping import Mapping + +from api.dfg.macros.LeftMergeEateries import LeftMergeEateries + +from api.dfg.wait_times.WaitTimes import WaitTimes +from api.dfg.wait_times.WaitTimeFilter import WaitTimeFilter + +main_dfg = DictResponseWrapper( + ConvertToJson( + InMemoryCache( + WaitTimeFilter( + LeftMergeEateries( + Mapping( + child=EateryStubs(), + fn = lambda eatery, cache: EateryGenerator( + eatery_id=eatery.id, + wait_times_dfg=WaitTimes(eatery.id, cache) + ) + ), + LeftMergeEateries( + Mapping( + child = EateryStubs(), + fn = lambda eatery, cache: EateryGenerator( + eatery_id=eatery.id, + events_dfg=EateryEvents(eatery.id, cache) + ) + ), + LeftMergeEateries( + EateriesFromDB(), + LeftMergeEateries( + CornellDiningNow(), + EateryStubs() + ) + ) + ) + ) + ) + ) + ), + re_raise_exceptions=True +) + + +def index(request): + tzinfo = pytz.timezone("US/Eastern") + reload = request.GET.get('reload') + result = main_dfg( + tzinfo=tzinfo, + reload=reload is not None and reload != "false", + start=date.today(), + end=date.today() + timedelta(days=7) + ) + return JsonResponse(result) + + +def google_sheets_eateries(request): + # dfg = DictResponseWrapper( + # EateryToJson( + # GoogleSheetsEateries( + # spreadsheet_id="1ImfeTUA6I1Ub-aavgIW53Pf7EVB694f1294NPSCRd5c" + # ) + # ) + # ) + + # result = dfg( + # tzinfo=pytz.timezone("US/Eastern"), + # start=date.today(), + # end=date.today() + timedelta(days=7) + # ) + + return JsonResponse([]) diff --git a/src/eateries/__init__.py b/src/eateries/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/eateries/admin.py b/src/eateries/admin.py new file mode 100644 index 0000000..1a1a610 --- /dev/null +++ b/src/eateries/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin + +import eateries.models as models +# Register your models here. + +admin.site.register(models.EateryStore) +admin.site.register(models.MenuStore) +admin.site.register(models.ItemStore) +admin.site.register(models.SubItemStore) +admin.site.register(models.AlertStore) +admin.site.register(models.CategoryStore) +admin.site.register(models.CategoryItemAssociation) +admin.site.register(models.DayOfWeekEventSchedule) +admin.site.register(models.DateEventSchedule) +admin.site.register(models.ClosedEventSchedule) \ No newline at end of file diff --git a/src/eateries/apps.py b/src/eateries/apps.py new file mode 100644 index 0000000..6a490f8 --- /dev/null +++ b/src/eateries/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EateriesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'eateries' diff --git a/src/eateries/management/commands/create_db_snapshot.py b/src/eateries/management/commands/create_db_snapshot.py new file mode 100644 index 0000000..5d87397 --- /dev/null +++ b/src/eateries/management/commands/create_db_snapshot.py @@ -0,0 +1,62 @@ + +from django.core.management.base import BaseCommand + +from datetime import datetime +from pathlib import Path + +from util.constants import SnapshotFileName + +import eateries.models as models +import eateries.serializers as serializers +import pytz +import json + +class Command(BaseCommand): + help = 'Saves the current state of the database' + + def write_to_file(self, serializer, db_objects, folder_path, file_name_enum: SnapshotFileName): + file_path = f"{folder_path}/{file_name_enum.value}" + serialized_lst = serializer(db_objects, many=True) + with open(file_path, "w") as file: + for obj in serialized_lst.data: + file.write(json.dumps(obj) + "\n") + + def handle(self, *args, **options): + tzinfo = pytz.timezone("US/Eastern") + time = datetime.now(tzinfo).strftime("%Y-%m-%d %H:%M:%S") + folder_path = f"db_snapshots/{time}" + Path(folder_path).mkdir(parents=True, exist_ok=True) + + eateries = models.EateryStore.objects.all() + self.write_to_file(serializers.EateryStoreSerializer, eateries, folder_path, SnapshotFileName.EATERY_STORE) + + alerts = models.AlertStore.objects.filter(end_timestamp__gte=datetime.now().timestamp()) + self.write_to_file(serializers.AlertStoreSerializer, alerts, folder_path, SnapshotFileName.ALERT_STORE) + + menus = models.MenuStore.objects.all() + self.write_to_file(serializers.MenuStoreSerializer, menus, folder_path, SnapshotFileName.MENU_STORE) + + categories = models.CategoryStore.objects.all() + self.write_to_file(serializers.CategoryStoreSerializer, categories, folder_path, SnapshotFileName.CATEGORY_STORE) + + items = models.ItemStore.objects.all() + self.write_to_file(serializers.ItemStoreSerializer, items, folder_path, SnapshotFileName.ITEM_STORE) + + subitems = models.SubItemStore.objects.all() + self.write_to_file(serializers.SubItemStoreSerializer, subitems, folder_path, SnapshotFileName.SUBITEM_STORE) + + category_item_associations = models.CategoryItemAssociation.objects.all() + self.write_to_file(serializers.CategoryItemAssociationSerializer, category_item_associations, folder_path, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) + + # date_event_schedules = models.DateEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) + # self.write_to_file(date_event_schedules, f"{folder_path}/{SnapshotFileName.DATE_EVENT_SCHEDULE}") + + # closed_event_schedules = models.ClosedEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) + # self.write_to_file(closed_event_schedules, f"{folder_path}/{SnapshotFileName.CLOSED_EVENT_SCHEDULE}") + + day_of_week_event_schedules = models.DayOfWeekEventSchedule.objects.all() + self.write_to_file(serializers.DayOfWeekEventScheduleSerializer, day_of_week_event_schedules, folder_path, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) + + # # TODO: Need to filter here for only the valid schedules + # event_schedules = models.EventSchedule.objects.all() + # self.write_to_file(event_schedules, f"{folder_path}/{SnapshotFileName.EVENT_SCHEDULE}") \ No newline at end of file diff --git a/src/eateries/management/commands/ingest_db_snapshot.py b/src/eateries/management/commands/ingest_db_snapshot.py new file mode 100644 index 0000000..ff7a8a0 --- /dev/null +++ b/src/eateries/management/commands/ingest_db_snapshot.py @@ -0,0 +1,31 @@ + +from django.core.management.base import BaseCommand +from util.constants import SnapshotFileName + +import eateries.serializers as serializers +import json + +class Command(BaseCommand): + help = 'Overrides current state of the db with a db snapshot' + + # Only writes data if the table has been flushed + def ingest_data(self, serializer, file_name: SnapshotFileName): + folder_path = "db_snapshots/2022-01-10 13:05:44" + with open(f"{folder_path}/{file_name.value}", "r") as file: + json_objs = [] + for line in file: + if (len(line) > 2): + json_objs.append(json.loads(line)) + serialized_objs = serializer(data=json~_objs, many=True) + serialized_objs.is_valid() + serialized_objs.save() + + def handle(self, *args, **options): + self.ingest_data(serializers.EateryStoreSerializer, SnapshotFileName.EATERY_STORE) + self.ingest_data(serializers.AlertStoreSerializer, SnapshotFileName.ALERT_STORE) + self.ingest_data(serializers.MenuStoreSerializer, SnapshotFileName.MENU_STORE) + self.ingest_data(serializers.CategoryStoreSerializer, SnapshotFileName.CATEGORY_STORE) + self.ingest_data(serializers.ItemStoreSerializer, SnapshotFileName.ITEM_STORE) + self.ingest_data(serializers.SubItemStoreSerializer, SnapshotFileName.SUBITEM_STORE) + self.ingest_data(serializers.CategoryItemAssociationSerializer, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) + self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) \ No newline at end of file diff --git a/src/eateries/migrations/0001_initial.py b/src/eateries/migrations/0001_initial.py new file mode 100644 index 0000000..a571daa --- /dev/null +++ b/src/eateries/migrations/0001_initial.py @@ -0,0 +1,88 @@ +# Generated by Django 4.0 on 2022-01-10 17:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='EateryStore', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=40)), + ('menu_summary', models.CharField(blank=True, max_length=60)), + ('image_url', models.URLField(blank=True)), + ('location', models.CharField(blank=True, max_length=30)), + ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), + ('online_order', models.BooleanField(blank=True, null=True)), + ('online_order_url', models.URLField(blank=True)), + ('latitude', models.FloatField(blank=True, null=True)), + ('longitude', models.FloatField(blank=True, null=True)), + ('payment_accepts_meal_swipes', models.BooleanField(blank=True, null=True)), + ('payment_accepts_brbs', models.BooleanField(blank=True, null=True)), + ('payment_accepts_cash', models.BooleanField(blank=True, null=True)), + ], + ), + migrations.CreateModel( + name='ItemStore', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=40)), + ('description', models.CharField(blank=True, max_length=200)), + ('base_price', models.FloatField(blank=True, null=True)), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.CreateModel( + name='SubItemStore', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('additional_price', models.FloatField(blank=True, null=True)), + ('total_price', models.FloatField(blank=True, null=True)), + ('name', models.CharField(max_length=40)), + ('item_subsection', models.CharField(max_length=40)), + ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), + ], + ), + migrations.CreateModel( + name='MenuStore', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=40)), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.CreateModel( + name='ExceptionStore', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.CharField(max_length=250)), + ('start_timestamp', models.IntegerField()), + ('end_timestamp', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.CreateModel( + name='CategoryStore', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category', models.CharField(max_length=40)), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ], + ), + migrations.CreateModel( + name='CategoryItemAssociation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.categorystore')), + ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), + ], + ), + ] diff --git a/src/eateries/migrations/0002_closedeventschedule_and_more.py b/src/eateries/migrations/0002_closedeventschedule_and_more.py new file mode 100644 index 0000000..358bf34 --- /dev/null +++ b/src/eateries/migrations/0002_closedeventschedule_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.0 on 2022-01-10 18:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ClosedEventSchedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('canonical_date', models.DateField()), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterUniqueTogether( + name='categoryitemassociation', + unique_together={('item', 'category')}, + ), + ] diff --git a/src/eateries/migrations/0003_closedeventschedule_eatery_and_more.py b/src/eateries/migrations/0003_closedeventschedule_eatery_and_more.py new file mode 100644 index 0000000..b09825b --- /dev/null +++ b/src/eateries/migrations/0003_closedeventschedule_eatery_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 4.0 on 2022-01-10 18:19 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0002_closedeventschedule_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='closedeventschedule', + name='eatery', + field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore'), + preserve_default=False, + ), + migrations.AddField( + model_name='closedeventschedule', + name='event_description', + field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), + preserve_default=False, + ), + ] diff --git a/src/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py b/src/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py new file mode 100644 index 0000000..de796ef --- /dev/null +++ b/src/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py @@ -0,0 +1,40 @@ +# Generated by Django 4.0 on 2022-01-10 18:23 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0003_closedeventschedule_eatery_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='DayOfWeekEventSchedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('day_of_week', models.CharField(choices=[('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday')], max_length=10)), + ('start', models.TimeField()), + ('end', models.TimeField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DateEventSchedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('canonical_date', models.DateField()), + ('start_timestamp', models.IntegerField()), + ('end_timestamp', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/src/eateries/migrations/0005_dateeventschedule_event_description_and_more.py b/src/eateries/migrations/0005_dateeventschedule_event_description_and_more.py new file mode 100644 index 0000000..e6a89a3 --- /dev/null +++ b/src/eateries/migrations/0005_dateeventschedule_event_description_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.0 on 2022-01-10 18:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0004_dayofweekeventschedule_dateeventschedule'), + ] + + operations = [ + migrations.AddField( + model_name='dateeventschedule', + name='event_description', + field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), + preserve_default=False, + ), + migrations.AddField( + model_name='dateeventschedule', + name='menu', + field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore'), + preserve_default=False, + ), + migrations.AddField( + model_name='dayofweekeventschedule', + name='event_description', + field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), + preserve_default=False, + ), + migrations.AddField( + model_name='dayofweekeventschedule', + name='menu', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore'), + preserve_default=False, + ), + ] diff --git a/src/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py b/src/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py new file mode 100644 index 0000000..0474536 --- /dev/null +++ b/src/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py @@ -0,0 +1,70 @@ +# Generated by Django 4.0 on 2022-01-10 18:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0005_dateeventschedule_event_description_and_more'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='categoryitemassociation', + unique_together=set(), + ), + migrations.AlterField( + model_name='categoryitemassociation', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='categorystore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='closedeventschedule', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='dateeventschedule', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='dayofweekeventschedule', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='exceptionstore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='itemstore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='menustore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='subitemstore', + name='id', + field=models.IntegerField(primary_key=True, serialize=False), + ), + migrations.AlterUniqueTogether( + name='categorystore', + unique_together={('menu', 'category')}, + ), + migrations.AlterUniqueTogether( + name='menustore', + unique_together={('eatery', 'name')}, + ), + ] diff --git a/src/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py b/src/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py new file mode 100644 index 0000000..e8d5d58 --- /dev/null +++ b/src/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0 on 2022-01-11 13:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('eateries', '0006_alter_categoryitemassociation_unique_together_and_more'), + ] + + operations = [ + migrations.RenameModel( + old_name='ExceptionStore', + new_name='AlertStore', + ), + migrations.AlterUniqueTogether( + name='dayofweekeventschedule', + unique_together={('eatery', 'day_of_week', 'event_description')}, + ), + ] diff --git a/src/eateries/migrations/__init__.py b/src/eateries/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/eateries/models.py b/src/eateries/models.py new file mode 100644 index 0000000..de25063 --- /dev/null +++ b/src/eateries/models.py @@ -0,0 +1,111 @@ +from django.db import models +from datetime import datetime +class EateryStore(models.Model): + class CampusArea(models.TextChoices): + WEST = 'West' + NORTH = 'North' + CENTRAL = 'Central' + COLLEGETOWN = 'Collegetown' + NONE = '' + + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=40, blank=True) + menu_summary = models.CharField(max_length = 60, blank=True) + image_url = models.URLField(blank=True) + location = models.CharField(max_length=30, blank=True) + campus_area = models.CharField(max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True) + online_order = models.BooleanField(null = True, blank=True) + online_order_url = models.URLField(blank=True) + latitude = models.FloatField(null = True, blank=True) + longitude = models.FloatField(null = True, blank=True) + payment_accepts_meal_swipes = models.BooleanField(null = True, blank=True) + payment_accepts_brbs = models.BooleanField(null = True, blank=True) + payment_accepts_cash = models.BooleanField(null = True, blank=True) + +class AlertStore(models.Model): + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + description = models.CharField(max_length = 250) + start_timestamp = models.IntegerField() + end_timestamp = models.IntegerField() + +class MenuStore(models.Model): + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + name = models.CharField(max_length = 40) + + class Meta: + unique_together = ('eatery', 'name') + +class CategoryStore(models.Model): + id = models.IntegerField(primary_key=True) + menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + category = models.CharField(max_length = 40) + + class Meta: + unique_together = ('menu', 'category') + +class ItemStore(models.Model): + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + name = models.CharField(max_length=40) + description = models.CharField(max_length = 200, blank=True) + base_price = models.FloatField(null = True, blank=True) + +class SubItemStore(models.Model): + id = models.IntegerField(primary_key=True) + item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) + additional_price = models.FloatField(null = True, blank=True) + total_price = models.FloatField(null = True, blank=True) + name = models.CharField(max_length=40) + item_subsection = models.CharField(max_length=40) + +class CategoryItemAssociation(models.Model): + id = models.IntegerField(primary_key=True) + item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) + category = models.ForeignKey(CategoryStore, on_delete=models.DO_NOTHING) +class EventDescription(models.TextChoices): + BREAKFAST = 'Breakfast' + BRUNCH = 'Brunch' + LUNCH = 'Lunch' + DINNER = 'Dinner' + GENERAL = 'General' + +class EventSchedule(models.Model): + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + class Meta: + abstract=True + +class DayOfWeekEventSchedule(EventSchedule): + + class DayOfTheWeek(models.TextChoices): + MONDAY = 'Monday' + TUESDAY = 'Tuesday' + WEDNESDAY = 'Wednesday' + THURSDAY = 'Thursday' + FRIDAY = 'Friday' + SATURDAY = 'Saturday' + SUNDAY = 'Sunday' + + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + menu= models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=10) + start = models.TimeField() + end = models.TimeField() + class Meta: + unique_together = ('eatery', 'day_of_week', 'event_description') + + +class DateEventSchedule(EventSchedule): + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + canonical_date = models.DateField() + start_timestamp = models.IntegerField() + end_timestamp = models.IntegerField() + +class ClosedEventSchedule(EventSchedule): + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + canonical_date = models.DateField() + + diff --git a/src/eateries/serializers.py b/src/eateries/serializers.py new file mode 100644 index 0000000..0dcc763 --- /dev/null +++ b/src/eateries/serializers.py @@ -0,0 +1,43 @@ +from rest_framework import serializers + +import eateries.models as models + +class EateryStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.EateryStore + fields = '__all__' + +class AlertStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.AlertStore + fields = '__all__' + +class MenuStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.MenuStore + fields = '__all__' + +class ItemStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.ItemStore + fields = '__all__' + +class SubItemStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.SubItemStore + fields = '__all__' + +class CategoryStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.CategoryStore + fields = '__all__' + +class CategoryItemAssociationSerializer(serializers.ModelSerializer): + class Meta: + model = models.CategoryItemAssociation + fields = '__all__' + +class DayOfWeekEventScheduleSerializer(serializers.ModelSerializer): + class Meta: + model = models.DayOfWeekEventSchedule + fields = '__all__' \ No newline at end of file diff --git a/src/eateries/tests.py b/src/eateries/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/eateries/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/eateries/views.py b/src/eateries/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/src/eateries/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/eatery_blue_backend/__init__.py b/src/eatery_blue_backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/eatery_blue_backend/asgi.py b/src/eatery_blue_backend/asgi.py new file mode 100644 index 0000000..87d9109 --- /dev/null +++ b/src/eatery_blue_backend/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for eatery_blue_backend project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") + +application = get_asgi_application() diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py new file mode 100644 index 0000000..f63a119 --- /dev/null +++ b/src/eatery_blue_backend/settings.py @@ -0,0 +1,127 @@ +""" +Django settings for eatery_blue_backend project. + +Generated by 'django-admin startproject' using Django 4.0. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.0/ref/settings/ +""" + +from pathlib import Path +import os + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-tup==8h@6!ewid!sfi*)jomsejj4j@=w=u*2ri9g0*0$3)1dkq" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(",") + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "transactions", + "eateries", + "rest_framework" +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "eatery_blue_backend.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "eatery_blue_backend.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.0/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.0/howto/static-files/ + +STATIC_URL = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py new file mode 100644 index 0000000..8db22b1 --- /dev/null +++ b/src/eatery_blue_backend/urls.py @@ -0,0 +1,22 @@ +"""eatery_blue_backend URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import include, path + +urlpatterns = [ + path("api", include("api.urls")), + path("admin/", admin.site.urls), +] diff --git a/src/eatery_blue_backend/wsgi.py b/src/eatery_blue_backend/wsgi.py new file mode 100644 index 0000000..3455c86 --- /dev/null +++ b/src/eatery_blue_backend/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for eatery_blue_backend project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") + +application = get_wsgi_application() diff --git a/src/manage.py b/src/manage.py new file mode 100755 index 0000000..b469b27 --- /dev/null +++ b/src/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/src/static_sources/cornell_dining_now_eateries.json b/src/static_sources/cornell_dining_now_eateries.json new file mode 100644 index 0000000..2d2ac1b --- /dev/null +++ b/src/static_sources/cornell_dining_now_eateries.json @@ -0,0 +1 @@ +{"status":"success","data":{"eateries":[{"id":31,"slug":"104-West","name":"104West!","nameshort":"104West!","about":"
\r\nLocated next to the Center for Jewish Living building on the south edge of west campus, 104West!<\/strong> is Cornell's kosher and multicultural dining room<\/a>. Menus are prepared under the supervision of STAR-K (meat and pareve) and STAR-D (dairy) Kosher Certifications, and Jewish dietary laws are strictly followed with the direction of a resident \"Mashgiach,\" or kosher-food supervisor.\r\n

\r\nYou don't have to keep kosher to enjoy the menu\u2013come sample traditional kosher entrees and enjoy mouth-watering ethnic and international options with a kosher flair. Dining options also include Halal, Seventh-day Adventist, vegetarian, vegan, and other diets.\r\n

\r\nShabbat dinner times vary over the course of the year, based on sunset times. Save money and help us plan by making advance reservations for Shabbat dinners and holiday meals at
https:\/\/kosher.scl.cornell.edu<\/a>!\r\n

\r\nMore Info:
104West!<\/strong><\/a>\r\n

\r\n
\"104West!<\/a>\r\n

","aboutshort":"Cornell's kosher and multicultural dining room is STAR-K and STAR-D certified.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"vlpa2hk9677m9bcbh6n2dtpn7k@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-272-6907","contactEmail":null,"serviceUnitId":9,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.444266,"longitude":-76.487598,"location":"104 West Avenue","coordinates":{"latitude":42.444266,"longitude":-76.487598},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":7,"slug":"Amit-Bhatia-Libe-Cafe","name":"Amit Bhatia Libe Caf\u00e9","nameshort":"Amit Bhatia Libe Caf\u00e9","about":"
A combined effort between Cornell Dining and Olin Library, the Amit Bhatia Libe Caf\u00e9<\/strong> serves specialty Starbucks coffees, smoothies, pastries, and Freshtake Grab-n-Go sandwiches, salads, and snacks.\r\n

\r\n\r\nMore Info:
Amit Bhatia Libe Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Amit<\/a>\r\n

","aboutshort":"The perfect place to take a study break, or to enjoy a latte and a pastry while you enjoy wireless Internet access on your laptop.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"g1pfs9edl1ks5o2dbc58e7fhm8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-4344","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448019,"longitude":-76.484499,"location":"Olin Library","coordinates":{"latitude":42.448019,"longitude":-76.484499},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640124000,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640210400,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640296800,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":8,"slug":"Atrium-Cafe","name":"Atrium Caf\u00e9","nameshort":"Atrium Caf\u00e9","about":"
The Atrium Caf\u00e9's<\/strong> coffee kiosk proudly serves Starbucks specialty coffee, pastries, and Grab-n-Go items, with extended hours. When class is in session, the coffee kiosk opens weekdays at 7am, and closes at 4pm Monday through Thursday, and 2pm Friday.\r\n

\r\nMore Info:
Atrium Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Atrium<\/a>\r\n

","aboutshort":"The Atrium Caf\u00e9 is located in historic Sage Hall, home to the Johnson Graduate School of Management. Coffee kiosk is open extended hours!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"9g3c81c0p2loacsbvrjj5o371c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7591","contactEmail":null,"serviceUnitId":9999,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.445784,"longitude":-76.483232,"location":"Sage Hall","coordinates":{"latitude":42.445784,"longitude":-76.483232},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":1,"slug":"Bear-Necessities","name":"Bear Necessities Grill & C-Store","nameshort":"Bear Necessities","about":"
The grill serves up deluxe burgers, hot chicken sandwiches, delicious french fries, and other mouth-watering comfort foods. You can also order 5 Star Subs and homemade pizza. Bear Necessities<\/strong> also has a convenience store with staple food, beverages and household items.\r\n

\r\n\r\nMore Info:
Bear Necessities<\/strong><\/a>\r\n

\r\n
\"Bear<\/a>\r\n

","aboutshort":"Bear Necessities is a convenience store and grill located on the first floor of Robert Purcell Community Center on north campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h319a8fk4b5lv0644ebkskhha8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-8227","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455777,"longitude":-76.477657,"location":"RPCC - first floor","coordinates":{"latitude":42.455777,"longitude":-76.477657},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640134800,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640221200,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Convenience Store","descrshort":"convenience store"}],"diningCuisines":[{"name":"Pizza","nameshort":"Pizza","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Grill - 5-Star Subs","category":"Grill","item":"5-Star Subs","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Pizza","category":"Pasta & Pizza","item":"Pizza","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Calzones","category":"Pasta & Pizza","item":"Calzones","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Misc - Mozzarella Sticks","category":"Misc","item":"Mozzarella Sticks","healthy":false,"showCategory":false},{"descr":"Misc - Onion Petals","category":"Misc","item":"Onion Petals","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":25,"slug":"Becker-House-Dining","name":"Becker House Dining Room","nameshort":"Becker House Dining","about":"
\r\nThe Becker House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
Becker House Dining Room<\/strong><\/a>\r\n

\r\n
\"Becker<\/a>\r\n

","aboutshort":"Dining room located in Carl Becker House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"di2s9rofto7m8innt5e8vftl0o@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8882","contactEmail":null,"serviceUnitId":2,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.448336,"longitude":-76.489606,"location":"Carl Becker House","coordinates":{"latitude":42.448336,"longitude":-76.489606},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":10,"slug":"Big-Red-Barn","name":"Big Red Barn","nameshort":"Big Red Barn","about":"
\r\nThe Big Red Barn<\/strong> is a cozy caf\u00e9 with a delicious breakfast and lunch menu. It also serves as Cornell's on-campus social center for graduate and professional students. Relax at outdoor picnic tables, or sit inside by the fireplace in comfortable lounge furniture.\r\n

\r\nMore Info:
Big Red Barn<\/strong><\/a>\r\n

\r\n
\"Big<\/a>\r\n

","aboutshort":"Once a carriage house, the Big Red Barn is now a cozy caf\u00e9 with a delicious breakfast and lunch menu.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"u1kmovdep2qlmr86io8h4p3ee8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0428","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448526,"longitude":-76.48098,"location":"Big Red Barn","coordinates":{"latitude":42.448526,"longitude":-76.48098},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":11,"slug":"Bug-Stop-Bagels","name":"Bus Stop Bagels","nameshort":"Bus Stop Bagels","about":"
\r\nBus Stop Bagels<\/strong>, next to the Trillium food court in Kennedy Hall, gets fresh bagels delivered twice a day from Ithaca Bakery with all the usual toppings and a great selection of house sandwiches, plus Starbucks coffee drinks and snack foods.\r\n

\r\nMore Info:
Bus Stop Bagels<\/strong><\/a>\r\n

\r\n
\"Bus<\/a>\r\n

","aboutshort":"Bagels for breakfast, bagels for lunch, bagels to go!","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"rpp0nrlp282t9h18hhol5f0dkc@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447839,"longitude":-76.479456,"location":"Kennedy Hall","coordinates":{"latitude":42.447839,"longitude":-76.479456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640116800,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640203200,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640289600,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Coffee Bar - 96oz Coffee2Go","category":"Coffee Bar","item":"96oz Coffee2Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":12,"slug":"Cafe-Jennie","name":"Caf\u00e9 Jennie","nameshort":"Caf\u00e9 Jennie","about":"
\r\nLocated in The Cornell Store, Caf\u00e9 Jennie is named for Jennie McGraw, the daughter of John McGraw, a wealthy industrialist and a founding Cornell Trustee. Stop by for a Peet's Coffee, delicious Cheesecake Factory baked goods, and a mouth-watering array of sandwiches and wraps.\r\n

\r\nMore Info:
Caf\u00e9 Jennie<\/strong><\/a>\r\n

\r\n
\"Caf\u00e9<\/a>\r\n

","aboutshort":"A caf\u00e9 and sandwich\/pastry shop located in The Cornell Store.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"geron0aq1ooj7jugmcmdc2s2cc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8095","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446851,"longitude":-76.484376,"location":"The Cornell Store","coordinates":{"latitude":42.446851,"longitude":-76.484376},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640098800,"endTimestamp":1640120400,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640185200,"endTimestamp":1640206800,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640271600,"endTimestamp":1640286000,"start":"10:00am","end":"2:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Javiva","category":"Coffee Bar","item":"Javiva","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":2,"slug":"Carols-Cafe","name":"Carol's Caf\u00e9","nameshort":"Carol's Caf\u00e9","about":"
\r\nCome visit Carol's Caf\u00e9<\/strong> and enjoy a menu featuring specialty coffee from Starbucks, smoothies, hot daily soup selections, sushi, fresh fruit and snacks, Freshtake Grab-n-Go sandwiches and salads, delicious pastries and baked goods and more.\r\n

\r\nMore Info:
Carol's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Carol's<\/a>\r\n

","aboutshort":"Warm, inviting caf\u00e9 at Carol Tatkon Center open to the whole campus community.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"05r2nhfjbnknmsccgd6u8dij0g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2257","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453236,"longitude":-76.479404,"location":"Carol Tatkon Center, Balch Hall","coordinates":{"latitude":42.453236,"longitude":-76.479404},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":26,"slug":"Cook-House-Dining","name":"Cook House Dining Room","nameshort":"Cook House Dining","about":"
\r\nThe Cook House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. The eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Cook House Dining Room<\/strong><\/a>\r\n

\r\n
\"Cook<\/a>\r\n

","aboutshort":"Dining room located in Alice Cook House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"27hli58rto1hpf15m3sbe54sak@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-9508","contactEmail":null,"serviceUnitId":1,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.44889,"longitude":-76.488919,"location":"Alice Cook House","coordinates":{"latitude":42.44889,"longitude":-76.488919},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":14,"slug":"Cornell-Dairy-Bar","name":"Cornell Dairy Bar","nameshort":"Cornell Dairy Bar","about":"
\r\nIn the beautifully renovated Stocking Hall on the east end of Tower Road, the Cornell Dairy Bar is a great place for breakfast, coffee, or even sweet treat like Cornell ice cream, sundaes and floats. Regular hours vary seasonally, especially weekend hours, so please check the specific hours on this page for updates.\r\n

\r\nMore Info:
Cornell Dairy Bar<\/strong><\/a>\r\n

\r\n
\"Cornell<\/a>\r\n

","aboutshort":"In the renovated Stocking Hall, the Cornell Dairy features a variety of delicious Cornell Dairy ice cream, sundaes, and floats.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"prvu4v0nr4eu94mqu9q7busa6g@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7660","contactEmail":"dairybar@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447301,"longitude":-76.471398,"location":"Stocking Hall","coordinates":{"latitude":42.447301,"longitude":-76.471398},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"icecream-pink"},{"id":41,"slug":"Crossings-Cafe","name":"Crossings Caf\u00e9","nameshort":"Crossings Caf\u00e9","about":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","aboutshort":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"loadevpceh49fl3c8cuheb2dvk@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.4558,"longitude":-76.479386,"location":"Toni Morrison Hall","coordinates":{"latitude":42.4558,"longitude":-76.479386},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640124000,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640210400,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Mexican - Quesadillas","category":"Mexican","item":"Quesadillas","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Peet's Coffee","category":"Coffee Bar","item":"Peet's Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":32,"slug":"frannys","name":"Franny's","nameshort":"Franny's","about":"One of Cornell's newest eateries is Franny's, a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls. You'll find a unique mix of Pan-Asian sandwiches, rice bowls and ramen, bao buns and fries, as well as refreshing Asian-inspired drinks.","aboutshort":"Franny's is a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls, featuring a unique mix of Asian-inspired cuisine.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekdays","googleCalendarId":"cornell.edu_26ui0ai54lp0cdp2m3j27ci86c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0293","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.451053,"longitude":-76.483884,"location":"Next to Sibley Hall","coordinates":{"latitude":42.451053,"longitude":-76.483884},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[{"name":"Asian","nameshort":"Asian","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Asian - Rice Bowls","category":"Asian","item":"Rice Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Noodle Bowls","category":"Asian","item":"Noodle Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Banh Mi Sandwiches","category":"Asian","item":"Banh Mi Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":16,"slug":"Goldies-Cafe","name":"Goldie's Caf\u00e9","nameshort":"Goldie's Caf\u00e9","about":"
\r\nGoldie's is a great location on Central Campus for breakfast, lunch, or a mid-day snack. Signature sandwiches \u2013 some served on German-style pretzel rolls \u2013 have become customer favorites, and a wide array of Freshtake Grab-n-Go items, snacks, and desserts are always on the menu.\r\n

\r\nMore Info:
Goldie's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Goldie's<\/a>\r\n

","aboutshort":"Conveniently located in the Physical Sciences Building on Central Campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"kb9ce5jj2f6oli3c90tc7j6peo@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-6775","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450064,"longitude":-76.481503,"location":"Physical Sciences Building","coordinates":{"latitude":42.450064,"longitude":-76.481503},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":15,"slug":"Green-Dragon","name":"Green Dragon","nameshort":"Green Dragon","about":"
\r\nStop by the Green Dragon<\/strong> and enjoy a hot specialty Finger Lakes Coffee Roasters coffee, Freshtake Grab-n-Go sandwiches and salads, kosher items, and delicious desserts.\r\n

\r\nMore Info:
Green Dragon<\/strong><\/a>\r\n

\r\n
\"Green<\/a>\r\n

","aboutshort":"A hot spot on central campus, especially for the students, faculty, and staff of College of Architecture, Art and Planning.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"7sii70faon9ta2vpoehr69415s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3327","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450948,"longitude":-76.484456,"location":"Sibley Hall","coordinates":{"latitude":42.450948,"longitude":-76.484456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":24,"slug":"Hot-Dog-Cart","name":"Hot Dog Cart","nameshort":"Hot Dog Cart","about":"
\r\nCornell Dining's Hot Dog Cart<\/strong> is usually open outside Day Hall on summer weekdays when weather permits. On special occasions, the Hot Dog Cart may be elsewhere on campus. Keep an eye on Cornell Dining's
Facebook<\/a> and Twitter<\/a> for updates.\r\n

\r\nMore Info:
Hot Dog Cart<\/strong><\/a>\r\n

\r\n
\"Hot<\/a>\r\n

","aboutshort":"Enjoy lunch al fresco when weather permits, choosing an all-beef hot dog or vegetarian (tofu) hot dog.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"11:00am - 2:00pm weekdays, weather permitting","googleCalendarId":"cornell.edu_eaq3euadrebh0dmgqt618l7tgs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":34,"slug":"icecreamcart","name":"Ice Cream Bike","nameshort":"Ice Cream Bike","about":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","aboutshort":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekday middays for the summer, and Friday evenings for Cornell's concert series.","googleCalendarId":"cornell.edu_3kuppj4nsjes2b42jhpno5u97g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"icecream-pink"},{"id":27,"slug":"Jansens-Dining","name":"Jansen's Dining Room at Bethe House","nameshort":"Jansen's Dining","about":"
\r\nJansen's Dining Room at Bethe House<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Chef Jacob Kuehn puts together exciting new menus throughout the year. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Jansen's Dining Room at Bethe House<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Dining room located in Hans Bethe House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h0nfohf0d90ot1rmukjphj7ajc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1736","contactEmail":null,"serviceUnitId":5,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447116,"longitude":-76.48864,"location":"Hans Bethe House","coordinates":{"latitude":42.447116,"longitude":-76.48864},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":28,"slug":"Jansens-Market","name":"Jansen's Market","nameshort":"Jansen's Market","about":"
\r\nIn addition to an array of snacks, beverages, fresh and frozen take-away meals, household items, and pharmacy and beauty supplies, Jansen's Market<\/strong> also serve Starbucks coffee, bubble tea, smoothies, pastries, frozen yogurt, and Dreamfactory cheesecake.\r\n

\r\nMore Info:
Jansen's Market<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Full-service convenience store located on the first floor of Noyes Community Recreation Center on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"0dqnc6l2mt25okch8nimsnojhg@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-4997","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446325,"longitude":-76.487932,"location":"Noyes Community Recreation Center","coordinates":{"latitude":42.446325,"longitude":-76.487932},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Convenience Store","descrshort":"convenience store"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Bubble Tea","category":"Coffee Bar","item":"Bubble Tea","healthy":false,"showCategory":false},{"descr":"Misc - Peanut Butter Sandwich Bar","category":"Misc","item":"Peanut Butter Sandwich Bar","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":29,"slug":"Keeton-House-Dining","name":"Keeton House Dining Room","nameshort":"Keeton House Dining","about":"
\r\nThe Keeton House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Sample Chef Nery's unique menu offerings in the entr\u00e9e station, Asian station, grill, deli, and salad bar. This eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Keeton House Dining Room<\/strong><\/a>\r\n

\r\n
\"Keeton<\/a>\r\n

","aboutshort":"Dining room located in William Keeton House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ekd72jfc2qai617oloa2b0ibp0@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3033","contactEmail":null,"serviceUnitId":3,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446942,"longitude":-76.489992,"location":"William Keeton House","coordinates":{"latitude":42.446942,"longitude":-76.489992},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":42,"slug":"Mann-Cafe","name":"Mann Caf\u00e9","nameshort":"Mann Caf\u00e9","about":"Take a study break at Mann Caf\u00e9!","aboutshort":"Take a study break at Mann Caf\u00e9!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ppbukfcjo05629gk0t4fu26aro@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448799,"longitude":-76.47851,"location":"Mann Library on the Ag Quad","coordinates":{"latitude":42.448799,"longitude":-76.47851},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Mexican - Burritos","category":"Mexican","item":"Burritos","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":18,"slug":"Marthas-Cafe","name":"Martha's Caf\u00e9","nameshort":"Martha's Cafe","about":"
\r\nMartha's Caf\u00e9<\/strong>, in Martha Van Rensselaer Hall, offers made-to-order Mediterranean-inspired grain and salad bowls and wraps, composed bowls, Copper Horse Coffee, and breakfast!\r\n

\r\nMore Info:
Martha's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Martha's<\/a>\r\n

","aboutshort":"Fresh food with a Mediterranean flair.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sperf092mrbt796rr36toeqrus@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-8080","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450115,"longitude":-76.479237,"location":"Martha Van Rensselaer Hall","coordinates":{"latitude":42.450115,"longitude":-76.479237},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Milk","category":"Cornell Dairy","item":"Milk","healthy":false,"showCategory":true},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false},{"descr":"Lunch - Hot and Cold Mediterranean Bowls","category":"Lunch","item":"Hot and Cold Mediterranean Bowls","healthy":true,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":19,"slug":"Mattins-Cafe","name":"Mattin's Caf\u00e9","nameshort":"Mattin's Caf\u00e9","about":"
\r\nYou\u2019ll find Mattin's Caf\u00e9<\/strong> in the atrium of Duffield Hall, adjacent to Phillips Hall on the Engineering Quad. Enjoy made-to-order deli sandwiches, boneless wings, hot Starbucks coffee, Freshtake Grab-n-Go items, soups, kosher items, mouth-watering pastries and more.\r\n

\r\nMore Info:
Mattin's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Mattin's<\/a>\r\n

","aboutshort":"Popular with engineers and a go to spot in the stunning atrium of Duffield Hall.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"1qman2n728pqjuq5ntaoofc7v0@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-4581","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.444162,"longitude":-76.482287,"location":"Duffield Hall","coordinates":{"latitude":42.444162,"longitude":-76.482287},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640113200,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640199600,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":33,"slug":"mccormicks","name":"McCormick's at Moakley House","nameshort":"McCormick's","about":"McCormick's at Moakley House offers a casual environment close to campus, open daily through the season. Treat a campus visitor to an impeccably served luncheon, stop for refreshments after a round of golf, meet coworkers at the end of the day for a burger and a beer, or bring the family for a relaxing weekend brunch.\r\n\r\nNamed for Jack McCormick, a varsity golfer from the Cornell Class of 1957 whose will included a bequest to modernize Moakley House, this eatery alongside the award-winning Robert Trent Jones Golf Course at Cornell University isn't just for golfers. It's open to the public and has plenty of parking, and we welcome everyone for a drink, a snack, or a meal.\r\n\r\nMcCormick's is closed for the 2021 season as of September 24th.\r\n\r\n

\r\nMore Info:
McCormick's at Moakley House<\/strong><\/a>\r\n

","aboutshort":"Named for Jack McCormick, a varsity golfer from the Cornell Class of 1957, McCormick's is open to the public and has plenty of parking.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"cornell.edu_gqlmrg7n0qk8qihl75ru2u1p80@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-6536","contactEmail":"mccormicks@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.458324,"longitude":-76.469539,"location":"Robert Trent Jones Golf Course","coordinates":{"latitude":42.458324,"longitude":-76.469539},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false}],"announcements":[{"id":199,"title":"McCormick's is closed for the 2021 season after Friday, September 24th.","announceType":"EATERY","startTimestamp":1632283200,"stopTimestamp":1640408340}],"icon":"coffee-brown"},{"id":3,"slug":"North-Star","name":"North Star Dining Room","nameshort":"North Star","about":"
\r\nThe North Star Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Enjoy an open kitchen concept and numerous unique food stations serving a huge variety of ethnic and regional American cuisine. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
North Star Dining Room<\/strong><\/a>\r\n

\r\n
\"North<\/a>\r\n

","aboutshort":"Dining room located in Appel Commons on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ecbhqf3ibeei09dds91viod5g8@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2992","contactEmail":null,"serviceUnitId":7,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453527,"longitude":-76.475944,"location":"Appel Commons, Third floor","coordinates":{"latitude":42.453527,"longitude":-76.475944},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[{"id":216,"title":"North Star Dining Room is closed for renovation through the Spring 2022 semester!","announceType":"EATERY","startTimestamp":1639976400,"stopTimestamp":1654055940}],"icon":"restaurant-red"},{"id":20,"slug":"Okenshields","name":"Okenshields","nameshort":"Okenshields","about":"
\r\nOkenshields<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. With hundreds of menu options to choose from \u2013 from an Asian station to a healthy foods bar featuring whole grain salads and cut fruit \u2013 you're sure to find something that meets your fancy.\r\n

\r\nMore Info:
Okenshields<\/strong><\/a>\r\n

\r\n
\"Okenshields<\/a>\r\n

","aboutshort":"Dining room located in Willard Straight Hall on Central Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"3hku0mr66kapq1lh8fakug9kko@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6636","contactEmail":null,"serviceUnitId":10,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446491,"longitude":-76.485678,"location":"Willard Straight Hall","coordinates":{"latitude":42.446491,"longitude":-76.485678},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":4,"slug":"Risley-Dining","name":"Risley Dining Room","nameshort":"Risley Dining Room","about":"
\r\nRisley Dining Room<\/strong> is a 100% gluten-free, tree-nut-free, and peanut-free
dining room<\/a> for house residents, and for the entire Cornell community. There are always vegan and vegetarian choices. Modeled after the Christchurch Refectory at Oxford University, the dining room maintains the same Gothic charm as when it first opened as the all-Ivy \"Risley Great Hall\" in 1913. \r\n

\r\nMore Info:
Risley Dining Room<\/strong><\/a>\r\n

\r\n
\"Risley<\/a>\r\n

","aboutshort":"Dining room located in Risley Residential College on North Campus.","nutrition":"Risley Dining Room is certified 100% gluten-free, tree-nut-free, and peanut-free. Thank you for not bringing outside food into Risley!<\/strong>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"hq98btd396f3077p88d30c84fs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-4229","contactEmail":null,"serviceUnitId":8,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453117,"longitude":-76.481946,"location":"Risley Residential College","coordinates":{"latitude":42.453117,"longitude":-76.481946},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":5,"slug":"RPCC-Marketplace","name":"Robert Purcell Marketplace Eatery","nameshort":"RPCC Marketplace","about":"
\r\nThe Robert Purcell Marketplace Eatery<\/strong> is an award-winning
dining room<\/a> with a huge variety of menu options. Chef Kevin Moore, who has been with Cornell Dining for over 25 years, most recently at 104West!<\/a>, plans creative menus with roots in a variety of international traditions. Note: Our Late Dinner hours feature a limited menu.\r\n

\r\nMore Info:
Robert Purcell Marketplace Eatery<\/strong><\/a>\r\n

\r\n
\"Robert<\/a>\r\n

","aboutshort":"Dining room located in Robert Purcell Community Center on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"32uglqeiqfo9edhpp4tka8oqsc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1138","contactEmail":null,"serviceUnitId":6,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455973,"longitude":-76.477354,"location":"RPCC, Third floor","coordinates":{"latitude":42.455973,"longitude":-76.477354},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640088000,"endTimestamp":1640100600,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":2,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Pork Breakfast Sausage","healthy":false,"sortIdx":3},{"item":"Home Fries","healthy":true,"sortIdx":4},{"item":"Pancakes with Syrup","healthy":false,"sortIdx":5},{"item":"French Toast Sticks","healthy":false,"sortIdx":6},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":7}]},{"category":"Grill Station","sortIdx":3,"items":[{"item":"Scrambled Tofu","healthy":true,"sortIdx":1},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":2}]},{"category":"Specialty Station","sortIdx":4,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":2},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":3},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":4},{"item":"Waffle Bar","healthy":false,"sortIdx":5},{"item":"Assorted Cereal","healthy":false,"sortIdx":6},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":7}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640100600,"endTimestamp":1640115000,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":5,"items":[{"item":"Beef Vegetable Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":6,"items":[{"item":"Salad Bar","healthy":true,"sortIdx":1},{"item":"Grains For Brains","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":7,"items":[{"item":"Pasta Primavera","healthy":false,"sortIdx":1},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":8,"items":[{"item":"Potato Tots","healthy":false,"sortIdx":1},{"item":"Calabacitas","healthy":true,"sortIdx":2},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":9,"items":[{"item":"Hacienda Cheese Quesadilla","healthy":false,"sortIdx":1},{"item":"Grilled Cheese Sandwich","healthy":false,"sortIdx":2}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640124000,"endTimestamp":1640133000,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":10,"items":[{"item":"Cornell Chicken Noodle Soup","healthy":false,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":11,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2},{"item":"Grains For Brains","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":12,"items":[{"item":"Salmon","healthy":false,"sortIdx":1},{"item":"Spanish Style Brown Rice & Beans","healthy":true,"sortIdx":2},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Sides","sortIdx":13,"items":[{"item":"Steamed Vegetable Melange","healthy":true,"sortIdx":1},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":2},{"item":"Sauteed Zucchini","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4},{"item":"Fried Potato Puffs","healthy":false,"sortIdx":5}]},{"category":"Grill Station","sortIdx":14,"items":[{"item":"BBQ Chicken Pizza","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640174400,"endTimestamp":1640187000,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":15,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Turkey Breakfast Sausage","healthy":true,"sortIdx":3},{"item":"Bacon","healthy":false,"sortIdx":4},{"item":"Steamed Brown Rice","healthy":true,"sortIdx":5},{"item":"Home Fries","healthy":true,"sortIdx":6}]},{"category":"Grill Station","sortIdx":16,"items":[{"item":"Bacon & Cheese Omelet","healthy":false,"sortIdx":1},{"item":"Steamed Fresh Vegetables","healthy":true,"sortIdx":2},{"item":"Western Scrambled Tofu","healthy":true,"sortIdx":3},{"item":"Build Your Own Omelette","healthy":true,"sortIdx":4},{"item":"Cheese Omelet","healthy":false,"sortIdx":5}]},{"category":"Specialty Station","sortIdx":17,"items":[{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":1},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":2},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":3},{"item":"Waffle Bar","healthy":false,"sortIdx":4},{"item":"Assorted Cereal","healthy":false,"sortIdx":5},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":6}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640187000,"endTimestamp":1640201400,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":18,"items":[{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":19,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Salad Bar","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":20,"items":[{"item":"Pasta with Sun-dried Tomato Basil & Feta","healthy":true,"sortIdx":1},{"item":"Kale Pesto Chicken","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":21,"items":[{"item":"Chef's Choice Seasonal Vegetable","healthy":true,"sortIdx":1},{"item":"Seared Kale","healthy":true,"sortIdx":2},{"item":"Rosemary Roasted Red Skin Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":22,"items":[{"item":"Grilled Chicken Breast","healthy":true,"sortIdx":1},{"item":"Grilled Hot Dogs","healthy":false,"sortIdx":2},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4}]},{"category":"Pizza Station","sortIdx":23,"items":[{"item":"Supreme Vegetable Pizza","healthy":false,"sortIdx":1},{"item":"White Garlic Hawaiian Pizza","healthy":false,"sortIdx":2}]},{"category":"Wok\/Asian Station","sortIdx":24,"items":[{"item":"Moo Goo Gai Pan","healthy":false,"sortIdx":1},{"item":"Vegetable Fried Rice","healthy":false,"sortIdx":2},{"item":"Long Grain Brown Rice","healthy":true,"sortIdx":3},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":4}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640210400,"endTimestamp":1640219400,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":25,"items":[{"item":"Cornell Cream of Mushroom Soup","healthy":false,"sortIdx":1},{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":2}]},{"category":"Salad Bar Station","sortIdx":26,"items":[{"item":"Grains For Brains","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Entrees","sortIdx":27,"items":[{"item":"Roasted Eggplant & Zucchini Casserole","healthy":false,"sortIdx":1},{"item":"Chef's Choice Vegan Entree","healthy":false,"sortIdx":2},{"item":"Chef's Choice Meat Entree","healthy":false,"sortIdx":3},{"item":"Roasted Pork Loin with Mango Mojo","healthy":true,"sortIdx":4},{"item":"Gourmet Pretzel Bar","healthy":false,"sortIdx":5}]},{"category":"Hot Traditional Station - Sides","sortIdx":28,"items":[{"item":"Steamed Winter Vegetables","healthy":true,"sortIdx":1},{"item":"Sauteed Super Greens","healthy":true,"sortIdx":2},{"item":"Roasted Garlic & Herb Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":29,"items":[{"item":"Veggie Burger","healthy":true,"sortIdx":1},{"item":"Char-Grilled Hamburgers","healthy":false,"sortIdx":2},{"item":"Build Your Own Nachos","healthy":false,"sortIdx":3}]},{"category":"Pasta and Pizza Station","sortIdx":30,"items":[{"item":"Pasta By Design","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2},{"item":"Broccoli Alfredo Pizza","healthy":false,"sortIdx":3},{"item":"Buffalo Chicken Pizza","healthy":false,"sortIdx":4}]},{"category":"Wok\/Asian Station","sortIdx":31,"items":[{"item":"Pancake Bar with Fruit Toppings & Syrups","healthy":false,"sortIdx":1},{"item":"Omelet Bar","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640260800,"endTimestamp":1640273400,"start":"7:00am","end":"10:30am","menu":[],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640273400,"endTimestamp":1640287800,"start":"10:30am","end":"2:30pm","menu":[],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640296800,"endTimestamp":1640305800,"start":"5:00pm","end":"7:30pm","menu":[],"calSummary":"Dinner"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":30,"slug":"Rose-House-Dining","name":"Rose House Dining Room","nameshort":"Rose House Dining","about":"
\r\nThe Rose House Dining Room<\/strong> is a
dning room<\/a> for house residents, and for the entire Cornell community. This eatery features traditional hot entrees, an Asian station, a grill, a deli, and a salad bar. Continental breakfast on Saturday and Sunday. Sample Chef Matt Seeber's daily menu options \u2013 you won't be disappointed!\r\n

\r\nMore Info:
Rose House Dining Room<\/strong><\/a>\r\n

\r\n
\"Rose<\/a>\r\n

","aboutshort":"Dining room located in Flora Rose House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"mo4mqfpe88ucqaer728ovfei18@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-0337","contactEmail":null,"serviceUnitId":4,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447813,"longitude":-76.488791,"location":"Flora Rose House","coordinates":{"latitude":42.447813,"longitude":-76.488791},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":21,"slug":"Rustys","name":"Rusty's","nameshort":"Rusty's","about":"
\r\nConveniently located in the lobby of Uris Hall, Rusty's<\/strong> offers Starbucks specialty coffees, Straight from the Oven baked goods, Freshtake Grab-n-Go sandwiches, salads and more. At any time of day, you're sure to find something to whet your appetite!\r\n

\r\nMore Info:
Rusty's<\/strong><\/a>\r\n

\r\n
\"Rusty's<\/a>\r\n

","aboutshort":"A great place to grab a quick coffee or a bite to eat on your way to class or work.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sqp9nd9rt727fm7v2sgmfelkps@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6656","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447399,"longitude":-76.482302,"location":"Uris Hall","coordinates":{"latitude":42.447399,"longitude":-76.482302},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":13,"slug":"StraightMarket","name":"Straight from the Market","nameshort":"Straight from the Market","about":"With a farm-fresh marketplace flair, Straight from the Market offers a wide variety of fresh and marinated vegetables, hummus and tapenade bar, salad fixings, and Cornell Dairy ice cream on the main floor of Willard Straight Hall adjacent to the Straight Terrace. Healthy, flavorful, and ready-to-go meat, vegan, and vegetarian choices daily.","aboutshort":"A farm-fresh marketplace on the main floor of the Straight.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ju94n6trv0ccoqcnd5u7otle50@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3963","contactEmail":null,"serviceUnitId":11,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446372,"longitude":-76.485786,"location":"Willard Straight Hall","coordinates":{"latitude":42.446372,"longitude":-76.485786},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":23,"slug":"Trillium","name":"Trillium","nameshort":"Trillium","about":"
\r\nAt Trillium<\/strong> choose from a wide variety of food stations serving Mexican food, Asian dishes, sumptuous burgers, pasta bar, sandwiches, salads, soups, and made-to-order omelets, among many other delectable menu options.\r\n

\r\nMore Info:
Trillium<\/strong><\/a>\r\n

\r\n
\"Trillium<\/a>\r\n

","aboutshort":"Located in Kennedy Hall in the heart of Central Campus, Trillium is one of our most popular food courts.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"i8v43jd76mugc62voucp4dqn9s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447883,"longitude":-76.479127,"location":"Kennedy Hall","coordinates":{"latitude":42.447883,"longitude":-76.479127},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Food Court","descrshort":"food court"}],"diningCuisines":[{"name":"Mexican","nameshort":"Mexican","descr":""},{"name":"Asian","nameshort":"Asian","descr":""},{"name":"Pizza","nameshort":"Pizza","descr":""},{"name":"Italian","nameshort":"Italian","descr":null}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"fastfood-blue"}]},"message":null,"meta":{"copyright":"Cornell University, Cornell Dining","responseDttm":"2021-12-22T11:15:18-0500"}} \ No newline at end of file diff --git a/src/static_sources/external_eateries.json b/src/static_sources/external_eateries.json new file mode 100644 index 0000000..a2cf74f --- /dev/null +++ b/src/static_sources/external_eateries.json @@ -0,0 +1,600 @@ +{ + "eateries": [ + { + "id": 33, + "slug": "Terrace", + "external": true, + "name": "Terrace Restaurant", + "nameshort": "Terrace", + "about": "", + "contactPhone": "1-800-541-2501", + "coordinates": { + "latitude": 42.446267, + "longitude": -76.482314 + }, + "location": "Statler", + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "monday-friday", + "events": [ + { + "descr": "General", + "start": "10:00am", + "end": "3:00pm", + "menu": [] + } + ] + } + ], + "datesClosed": [ + "9/6/21", + "11/25/21", + "11/26/21", + "11/27/21", + "11/28/21", + "12/20/21-1/20/22" + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + }, + { + "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", + "descrshort": "Meal Plan - Debit" + } + ], + "diningItems": [ + { + "item": "Salads", + "healthy": true, + "category": "General" + }, + { + "item": "Burrito Bowl", + "healthy": false, + "category": "General" + }, + { + "item": "Burrito", + "healthy": true, + "category": "General" + }, + { + "item": "Chicken Tenders", + "healthy": false, + "category": "General" + }, + { + "item": "Fries", + "healthy": false, + "category": "General" + }, + { + "item": "Pho", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 34, + "slug": "Macs", + "external": true, + "name": "Mac's Café", + "nameshort": "Mac's", + "about": "", + "cornellDining": false, + "contactPhone": "1-800-541-2501", + "coordinates": { + "latitude": 42.445921, + "longitude": -76.481984 + }, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Statler Hotel", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "monday-friday", + "events": [ + { + "descr": "General", + "start": "9:30am", + "end": "5:30pm", + "menu": [] + } + ] + } + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + }, + { + "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", + "descrshort": "Meal Plan - Debit" + } + ], + "datesClosed": [ + "8/18/21", + "8/19/21", + "8/20/21", + "8/21/21", + "8/22/21", + "8/23/21", + "9/6/21", + "10/9/21", + "10/10/21", + "10/11/21", + "10/12/21", + "11/24/21", + "11/25/21", + "11/26/21", + "11/27/21", + "11/28/21", + "12/16/21", + "12/17/21", + "12/20/21-1/20/22" + ], + "diningItems": [ + { + "item": "Pizza", + "healthy": false, + "category": "General" + }, + { + "item": "Pasta", + "healthy": false, + "category": "General" + }, + { + "item": "Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Sushi to Go", + "healthy": false, + "category": "General" + }, + { + "item": "Soft Drinks", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 35, + "slug": "Zeus", + "external": true, + "name": "Temple of Zeus", + "nameshort": "Temple of Zeus", + "about": "", + "cornellDining": false, + "contactPhone": "", + "coordinates": { + "latitude": 42.449091, + "longitude": -76.483414 + }, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Goldwin Smith Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "monday-friday", + "events": [ + { + "descr": "General", + "start": "8:00am", + "end": "5:00pm", + "menu": [] + } + ] + } + ], + "datesClosed": [ + "09/06/21" + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Sandwiches", + "healthy": true, + "category": "General" + }, + { + "item": "Soups", + "healthy": true, + "category": "General" + }, + { + "item": "Baked Goods", + "healthy": true, + "category": "General" + }, + { + "item": "Candy", + "healthy": false, + "category": "General" + }, + { + "item": "Soft Drinks", + "healthy": true, + "category": "General" + } + ] + }, + { + "id": 36, + "slug": "Gimme-Coffee", + "external": true, + "name": "Gimme Coffee", + "nameshort": "Gimme Coffee", + "about": "", + "cornellDining": false, + "contactPhone": "1-607-227-5391", + "coordinates": { + "latitude": 42.444958, + "longitude": -76.481169 + }, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Gates Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "monday-friday", + "events": [ + { + "descr": "General", + "start": "8:00am", + "end": "3:00pm", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Coffee", + "healthy": true, + "category": "General" + }, + { + "item": "Baked Goods", + "healthy": true, + "category": "General" + } + ] + }, + { + "id": 37, + "slug": "Louies-Lunch", + "external": true, + "name": "Louie's Lunch", + "nameshort": "Louie's", + "about": "", + "cornellDining": false, + "contactPhone": "1-607-257-4649", + "coordinates": { + "latitude": 42.45336, + "longitude": -76.481225 + }, + "campusArea": { + "descr": "North Campus", + "descrshort": "North" + }, + "location": "Across from Risley", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "monday-friday", + "events": [ + { + "descr": "General", + "start": "11:00am", + "end": "3:00am", + "menu": [] + } + ] + }, + { + "weekday": "saturday", + "events": [ + { + "descr": "General", + "start": "12:00pm", + "end": "3:00am", + "menu": [] + } + ] + }, + { + "weekday": "sunday", + "events": [ + { + "descr": "General", + "start": "6:00pm", + "end": "12:00am", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "French Fries", + "healthy": false, + "category": "General" + }, + { + "item": "Garlic Bread", + "healthy": false, + "category": "General" + }, + { + "item": "Cold Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Subs", + "healthy": false, + "category": "General" + }, + { + "item": "Pizza Subs", + "healthy": false, + "category": "General" + }, + { + "item": "Burgers", + "healthy": false, + "category": "General" + }, + { + "item": "Egg Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Dogs", + "healthy": false, + "category": "General" + }, + { + "item": "Wraps", + "healthy": false, + "category": "General" + }, + { + "item": "Salads", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 38, + "slug": "Anabels-Grocery", + "external": true, + "name": "Anabel's Grocery", + "nameshort": "Anabel's", + "about": "", + "cornellDining": false, + "contactPhone": "", + "coordinates": { + "latitude": 42.445061, + "longitude": -76.485826 + }, + "campusArea": { + "descr": "South Campus", + "descrshort": "South" + }, + "location": "Anabel Taylor Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "wednesday-thursday", + "events": [ + { + "descr": "General", + "start": "3:00pm", + "end": "7:00pm", + "menu": [] + } + ] + }, + { + "weekday": "friday", + "events": [ + { + "descr": "General", + "start": "12:00pm", + "end": "3:00pm", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Cornell Card", + "descrshort": "Cornell Card" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Whole Grains", + "healthy": true, + "category": "General" + }, + { + "item": "Spices", + "healthy": false, + "category": "General" + }, + { + "item": "Legumes", + "healthy": true, + "category": "General" + }, + { + "item": "Fresh and Frozen Produce", + "healthy": true, + "category": "General" + }, + { + "item": "Nuts", + "healthy": true, + "category": "General" + }, + { + "item": "Dried Fruits", + "healthy": true, + "category": "General" + }, + { + "item": "Tofu", + "healthy": true, + "category": "General" + }, + { + "item": "Eggs", + "healthy": false, + "category": "General" + }, + { + "item": "Milks", + "healthy": true, + "category": "General" + }, + { + "item": "Kombucha on Tap", + "healthy": true, + "category": "General" + }, + { + "item": "Bottled Drinks", + "healthy": false, + "category": "General" + }, + { + "item": "Bulk Items", + "healthy": false, + "category": "General" + } + ] + } + ] +} diff --git a/src/transactions/__init__.py b/src/transactions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/transactions/admin.py b/src/transactions/admin.py new file mode 100644 index 0000000..784107d --- /dev/null +++ b/src/transactions/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +from .models import TransactionHistory +# Register your models here. + +admin.site.register(TransactionHistory) \ No newline at end of file diff --git a/src/transactions/apps.py b/src/transactions/apps.py new file mode 100644 index 0000000..f806531 --- /dev/null +++ b/src/transactions/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TransactionsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'transactions' diff --git a/src/transactions/controllers/update_transactions_controller.py b/src/transactions/controllers/update_transactions_controller.py new file mode 100644 index 0000000..3c1245e --- /dev/null +++ b/src/transactions/controllers/update_transactions_controller.py @@ -0,0 +1,35 @@ +from datetime import datetime, timedelta +from transactions.models import TransactionHistory +from util.constants import vendor_name_to_internal_id + +import pytz +class UpdateTransactionsController: + + def __init__(self, data): + self._data = data + + def process(self): + if self._data["TIMESTAMP"] == "Invalid date": + return 0 + tz = pytz.timezone('America/New_York') + recent_datetime = tz.localize(datetime.strptime(self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p')) + canonical_date = recent_datetime.date() + block_end_time = recent_datetime.time() + if recent_datetime.hour < 4: + # between 12am and 4am associate this with the previous day + canonical_date = canonical_date - timedelta(days=1) + num_inserted = 0 + ignored_names = set() + for place in self._data["UNITS"]: + internal_id = vendor_name_to_internal_id(place["UNIT_NAME"]).value + if internal_id == None: + ignored_names.add(place["UNIT_NAME"]) + else: + num_inserted += 1 + try: + TransactionHistory.objects.create(eatery_id = internal_id, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) + except Exception as e: + print(e) + num_inserted -= 1 + return num_inserted + diff --git a/src/transactions/management/commands/fetch_recent_transactions.py b/src/transactions/management/commands/fetch_recent_transactions.py new file mode 100644 index 0000000..b264bf9 --- /dev/null +++ b/src/transactions/management/commands/fetch_recent_transactions.py @@ -0,0 +1,26 @@ +# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db + +import requests +import os +from requests.structures import CaseInsensitiveDict +from django.core.management.base import BaseCommand +from transactions.controllers.update_transactions_controller import UpdateTransactionsController + +class Command(BaseCommand): + help = 'Fetches transaction data from a vendor API and adds it to our transaction history database' + + def handle(self, *args, **options): + endpoint = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" + headers = CaseInsensitiveDict() + token = os.environ.get("CORNELL_VENDOR_TOKEN") + api_key = os.environ.get("CORNELL_VENDOR_API_KEY") + headers["Accept"] = "application/json" + headers["Authorization"] = "Bearer {}".format(token) + headers["X-Api-Key"] = api_key + resp = requests.get(endpoint, headers=headers) + num_inserted = 0 + if resp.status_code == 200: + res = UpdateTransactionsController(resp.json()).process() + if res["success"]: + num_inserted = res["result"]["num_inserted"] + # print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file diff --git a/src/transactions/management/commands/ingest_log_transactions.py b/src/transactions/management/commands/ingest_log_transactions.py new file mode 100644 index 0000000..82e713e --- /dev/null +++ b/src/transactions/management/commands/ingest_log_transactions.py @@ -0,0 +1,30 @@ +# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db + +import json +from datetime import datetime +from django.core.management.base import BaseCommand + +from transactions.controllers.update_transactions_controller import UpdateTransactionsController +from transactions.models import TransactionHistory +class Command(BaseCommand): + help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistory table' + + def handle(self, *args, **options): + num_deleted = TransactionHistory.objects.all().delete()[0] + counter = 0 + num_inserted = 0 + with open("static_sources/data.log", "r") as log: + for line in log: + try: + data = json.loads(line) + timestamp = datetime.strptime(data['TIMESTAMP'], '%Y-%m-%d %I:%M:%S %p') + if counter % 100 == 1: + print(timestamp) + if timestamp.year == 2021 and timestamp.month > 7: + counter += 1 + inserted = UpdateTransactionsController(data).process() + num_inserted += inserted + except Exception as e: + pass + print("{} Entries Deleted".format(num_deleted)) + print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file diff --git a/src/transactions/migrations/0001_initial.py b/src/transactions/migrations/0001_initial.py new file mode 100644 index 0000000..5178f47 --- /dev/null +++ b/src/transactions/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 4.0 on 2022-01-10 17:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('eateries', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='TransactionHistory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('canonical_date', models.DateField()), + ('block_end_time', models.TimeField()), + ('transaction_count', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.AddIndex( + model_name='transactionhistory', + index=models.Index(fields=['canonical_date'], name='transaction_canonic_a40422_idx'), + ), + migrations.AlterUniqueTogether( + name='transactionhistory', + unique_together={('eatery_id', 'block_end_time', 'canonical_date')}, + ), + ] diff --git a/src/transactions/migrations/__init__.py b/src/transactions/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/transactions/models.py b/src/transactions/models.py new file mode 100644 index 0000000..53d660b --- /dev/null +++ b/src/transactions/models.py @@ -0,0 +1,13 @@ +from django.db import models +from eateries.models import EateryStore +# Create your models here. + +# [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] +class TransactionHistory(models.Model): + class Meta: + unique_together = ('eatery_id', 'block_end_time', 'canonical_date') + indexes = [models.Index(fields = ['canonical_date'])] + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + canonical_date = models.DateField() + block_end_time = models.TimeField() + transaction_count = models.IntegerField() \ No newline at end of file diff --git a/src/transactions/tests.py b/src/transactions/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/transactions/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/transactions/views.py b/src/transactions/views.py new file mode 100644 index 0000000..b91e46a --- /dev/null +++ b/src/transactions/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. \ No newline at end of file diff --git a/src/util/constants.py b/src/util/constants.py new file mode 100644 index 0000000..b9108be --- /dev/null +++ b/src/util/constants.py @@ -0,0 +1,158 @@ +from enum import Enum + +from api.datatype.Eatery import EateryID + +CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" + +class SnapshotFileName(Enum): + EATERY_STORE = "eatery_store.txt" + ALERT_STORE = "alert_store.txt" + CATEGORY_STORE = "category_store.txt" + MENU_STORE = "menu_store.txt" + ITEM_STORE = "item_store.txt" + SUBITEM_STORE = "subitem_store.txt" + CATEGORY_ITEM_ASSOCIATION = "category_item_association.txt" + EVENT_SCHEDULE = "event_schedule.txt" + DAY_OF_WEEK_EVENT_SCHEDULE = "day_of_week_event_schedule.txt" + DATE_EVENT_SCHEDULE = "date_event_schedule.txt" + CLOSED_EVENT_SCHEDULE = "closed_event_schedule.txt" + TRANSACTION_HISTORY = "transaction_history.txt" + +def dining_id_to_internal_id(id: int): + if id == 31: + return EateryID.ONE_ZERO_FOUR_WEST + elif id == 7: + return EateryID.LIBE_CAFE + elif id == 8: + return EateryID.ATRIUM_CAFE + elif id == 1: + return EateryID.BEAR_NECESSITIES + elif id == 25: + return EateryID.BECKER_HOUSE + elif id == 10: + return EateryID.BIG_RED_BARN + elif id == 11: + return EateryID.BUS_STOP_BAGELS + elif id == 12: + return EateryID.CAFE_JENNIE + elif id == 2: + return EateryID.CAROLS_CAFE + elif id == 26: + return EateryID.COOK_HOUSE + elif id == 14: + return EateryID.DAIRY_BAR + elif id == 41: + return EateryID.CROSSINGS_CAFE + elif id == 32: + return EateryID.FRANNYS + elif id == 16: + return EateryID.GOLDIES_CAFE + elif id == 15: + return EateryID.GREEN_DRAGON + elif id == 24: + return EateryID.HOT_DOG_CART + elif id == 34: + return EateryID.ICE_CREAM_BIKE + elif id == 27: + return EateryID.BETHE_HOUSE + elif id == 28: + return EateryID.JANSENS_MARKET + elif id == 29: + return EateryID.KEETON_HOUSE + elif id == 42: + return EateryID.MANN_CAFE + elif id == 18: + return EateryID.MARTHAS_CAFE + elif id == 19: + return EateryID.MATTINS_CAFE + elif id == 33: + return EateryID.MCCORMICKS + elif id == 3: + return EateryID.NORTH_STAR_DINING + elif id == 20: + return EateryID.OKENSHIELDS + elif id == 4: + return EateryID.RISLEY + elif id == 5: + return EateryID.RPCC + elif id == 30: + return EateryID.ROSE_HOUSE + elif id == 21: + return EateryID.RUSTYS + elif id == 13: + return EateryID.STRAIGHT_FROM_THE_MARKET + elif id == 23: + return EateryID.TRILLIUM + else: + return None + +# Our transactions vendor +def vendor_name_to_internal_id(vendor_eatery_name): + vendor_eatery_name = ''.join(c.lower() for c in vendor_eatery_name if c.isalpha()) + if vendor_eatery_name == "bearnecessities": + return EateryID.BEAR_NECESSITIES + elif vendor_eatery_name == "northstarmarketplace": + return EateryID.NORTH_STAR_DINING + elif vendor_eatery_name == "jansensmarket": + return EateryID.JANSENS_MARKET + elif vendor_eatery_name == "stockinghallcafe" or vendor_eatery_name == "stockinghall": + return EateryID.DAIRY_BAR + elif vendor_eatery_name == "marthas": + return EateryID.MARTHAS_CAFE + elif vendor_eatery_name == "cafejennie": + return EateryID.CAFE_JENNIE + elif vendor_eatery_name == "goldiescafe": + return EateryID.GOLDIES_CAFE + elif vendor_eatery_name == "alicecookhouse": + return EateryID.COOK_HOUSE + elif vendor_eatery_name == "carlbeckerhouse": + return EateryID.BECKER_HOUSE + elif vendor_eatery_name == "duffield": + return EateryID.MATTINS_CAFE + elif vendor_eatery_name == "greendragon": + return EateryID.GREEN_DRAGON + elif vendor_eatery_name == "trillium": + return EateryID.TRILLIUM + elif vendor_eatery_name == "olinlibecafe": + return EateryID.LIBE_CAFE + elif vendor_eatery_name == "carolscafe": + return EateryID.CAROLS_CAFE + elif vendor_eatery_name == "statlerterrace": + return EateryID.TERRACE + elif vendor_eatery_name == "busstopbagels": + return EateryID.BUS_STOP_BAGELS + elif vendor_eatery_name == "kosher": + return EateryID.ONE_ZERO_FOUR_WEST + elif vendor_eatery_name == "jansensatbethehouse": + return EateryID.BETHE_HOUSE + elif vendor_eatery_name == "keetonhouse": + return EateryID.KEETON_HOUSE + elif vendor_eatery_name == "rpme": + return EateryID.RPCC + elif vendor_eatery_name == "rosehouse": + return EateryID.ROSE_HOUSE + elif vendor_eatery_name == "risley": + return EateryID.RISLEY + elif vendor_eatery_name == "frannysft": + return EateryID.FRANNYS + elif vendor_eatery_name == "mccormicks": + return EateryID.MCCORMICKS + elif vendor_eatery_name == "sage": + return EateryID.ATRIUM_CAFE + elif vendor_eatery_name == "straightmarket": + return EateryID.STRAIGHT_FROM_THE_MARKET + elif vendor_eatery_name == "crossingscafe": + return EateryID.CROSSINGS_CAFE + elif vendor_eatery_name == "okenshields": + return EateryID.OKENSHIELDS + elif vendor_eatery_name == "bigredbarn": + return EateryID.BIG_RED_BARN + elif vendor_eatery_name == "rustys": + return EateryID.RUSTYS + elif vendor_eatery_name == "manncafe": + return EateryID.MANN_CAFE + elif vendor_eatery_name == "statlermacs": + return EateryID.MACS_CAFE + else: + # TODO: Add a slack notif / flag that a wait time location was not recognized + return None \ No newline at end of file diff --git a/src/util/time.py b/src/util/time.py new file mode 100644 index 0000000..7fce11d --- /dev/null +++ b/src/util/time.py @@ -0,0 +1,5 @@ +from datetime import date, time, datetime +import pytz + +def combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: + return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) From 58249328a611eb16276ad2c62682bec05388e829 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Tue, 11 Jan 2022 10:03:20 -0500 Subject: [PATCH 046/305] Fix LeftMerge bug --- Dockerfile | 6 + api/__init__.py | 0 api/admin.py | 3 - api/apps.py | 6 - api/datatype/Eatery.py | 143 ----- api/datatype/EateryAlert.py | 30 - api/datatype/Event.py | 73 --- api/datatype/Menu.py | 15 - api/datatype/MenuCategory.py | 21 - api/datatype/MenuItem.py | 46 -- api/datatype/MenuItemSection.py | 20 - api/datatype/MenuSubItem.py | 28 - api/datatype/WaitTime.py | 29 - api/datatype/WaitTimesDay.py | 25 - api/dfg/CornellDiningNow.py | 59 -- api/dfg/DfgNode.py | 10 - api/dfg/EateriesFromDB.py | 80 --- api/dfg/EateryStubs.py | 11 - api/dfg/__init__.py | 0 api/dfg/macros/EateryEvents.py | 39 -- api/dfg/macros/LeftMergeEateries.py | 31 - api/dfg/macros/LeftMergeEvents.py | 35 - api/dfg/schedule/CacheMenuInjection.py | 87 --- api/dfg/schedule/ClosedSchedule.py | 19 - api/dfg/schedule/CornellDiningEvents.py | 113 ---- api/dfg/schedule/DateSchedule.py | 16 - api/dfg/schedule/DayOfWeekSchedule.py | 37 -- api/dfg/system/ConvertFromJson.py | 55 -- api/dfg/system/ConvertToJson.py | 34 - api/dfg/system/DictResponseWrapper.py | 29 - api/dfg/system/EateryGenerator.py | 25 - api/dfg/system/InMemoryCache.py | 75 --- api/dfg/system/LeftMerge.py | 54 -- api/dfg/system/Mapping.py | 20 - api/dfg/wait_times/WaitTimeFilter.py | 38 -- api/dfg/wait_times/WaitTimes.py | 115 ---- api/migrations/0001_initial.py | 25 - api/migrations/__init__.py | 0 api/tests.py | 3 - api/urls.py | 8 - api/views.py | 86 --- docker-compose.yml | 20 + eateries/__init__.py | 0 eateries/admin.py | 15 - eateries/apps.py | 6 - .../management/commands/create_db_snapshot.py | 62 -- .../management/commands/ingest_db_snapshot.py | 31 - eateries/migrations/0001_initial.py | 88 --- .../0002_closedeventschedule_and_more.py | 27 - ...003_closedeventschedule_eatery_and_more.py | 26 - ...ayofweekeventschedule_dateeventschedule.py | 40 -- ...ventschedule_event_description_and_more.py | 38 -- ...temassociation_unique_together_and_more.py | 70 -- ...name_exceptionstore_alertstore_and_more.py | 21 - eateries/migrations/__init__.py | 0 eateries/models.py | 111 ---- eateries/serializers.py | 43 -- eateries/tests.py | 3 - eateries/views.py | 3 - eatery_blue_backend/__init__.py | 0 eatery_blue_backend/asgi.py | 16 - eatery_blue_backend/settings.py | 127 ---- eatery_blue_backend/urls.py | 22 - eatery_blue_backend/wsgi.py | 16 - manage.py | 22 - src/api/dfg/system/LeftMerge.py | 8 +- .../cornell_dining_now_eateries.json | 1 - static_sources/external_eateries.json | 600 ------------------ transactions/__init__.py | 0 transactions/admin.py | 6 - transactions/apps.py | 6 - .../update_transactions_controller.py | 35 - .../commands/fetch_recent_transactions.py | 26 - .../commands/ingest_log_transactions.py | 30 - transactions/migrations/0001_initial.py | 34 - transactions/migrations/__init__.py | 0 transactions/models.py | 13 - transactions/tests.py | 3 - transactions/views.py | 3 - util/constants.py | 158 ----- util/time.py | 5 - 81 files changed, 32 insertions(+), 3252 deletions(-) create mode 100644 Dockerfile delete mode 100644 api/__init__.py delete mode 100644 api/admin.py delete mode 100644 api/apps.py delete mode 100644 api/datatype/Eatery.py delete mode 100644 api/datatype/EateryAlert.py delete mode 100644 api/datatype/Event.py delete mode 100644 api/datatype/Menu.py delete mode 100644 api/datatype/MenuCategory.py delete mode 100644 api/datatype/MenuItem.py delete mode 100644 api/datatype/MenuItemSection.py delete mode 100644 api/datatype/MenuSubItem.py delete mode 100644 api/datatype/WaitTime.py delete mode 100644 api/datatype/WaitTimesDay.py delete mode 100644 api/dfg/CornellDiningNow.py delete mode 100644 api/dfg/DfgNode.py delete mode 100644 api/dfg/EateriesFromDB.py delete mode 100644 api/dfg/EateryStubs.py delete mode 100644 api/dfg/__init__.py delete mode 100644 api/dfg/macros/EateryEvents.py delete mode 100644 api/dfg/macros/LeftMergeEateries.py delete mode 100644 api/dfg/macros/LeftMergeEvents.py delete mode 100644 api/dfg/schedule/CacheMenuInjection.py delete mode 100644 api/dfg/schedule/ClosedSchedule.py delete mode 100644 api/dfg/schedule/CornellDiningEvents.py delete mode 100644 api/dfg/schedule/DateSchedule.py delete mode 100644 api/dfg/schedule/DayOfWeekSchedule.py delete mode 100644 api/dfg/system/ConvertFromJson.py delete mode 100644 api/dfg/system/ConvertToJson.py delete mode 100644 api/dfg/system/DictResponseWrapper.py delete mode 100644 api/dfg/system/EateryGenerator.py delete mode 100644 api/dfg/system/InMemoryCache.py delete mode 100644 api/dfg/system/LeftMerge.py delete mode 100644 api/dfg/system/Mapping.py delete mode 100644 api/dfg/wait_times/WaitTimeFilter.py delete mode 100644 api/dfg/wait_times/WaitTimes.py delete mode 100644 api/migrations/0001_initial.py delete mode 100644 api/migrations/__init__.py delete mode 100644 api/tests.py delete mode 100644 api/urls.py delete mode 100644 api/views.py create mode 100644 docker-compose.yml delete mode 100644 eateries/__init__.py delete mode 100644 eateries/admin.py delete mode 100644 eateries/apps.py delete mode 100644 eateries/management/commands/create_db_snapshot.py delete mode 100644 eateries/management/commands/ingest_db_snapshot.py delete mode 100644 eateries/migrations/0001_initial.py delete mode 100644 eateries/migrations/0002_closedeventschedule_and_more.py delete mode 100644 eateries/migrations/0003_closedeventschedule_eatery_and_more.py delete mode 100644 eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py delete mode 100644 eateries/migrations/0005_dateeventschedule_event_description_and_more.py delete mode 100644 eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py delete mode 100644 eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py delete mode 100644 eateries/migrations/__init__.py delete mode 100644 eateries/models.py delete mode 100644 eateries/serializers.py delete mode 100644 eateries/tests.py delete mode 100644 eateries/views.py delete mode 100644 eatery_blue_backend/__init__.py delete mode 100644 eatery_blue_backend/asgi.py delete mode 100644 eatery_blue_backend/settings.py delete mode 100644 eatery_blue_backend/urls.py delete mode 100644 eatery_blue_backend/wsgi.py delete mode 100755 manage.py delete mode 100644 static_sources/cornell_dining_now_eateries.json delete mode 100644 static_sources/external_eateries.json delete mode 100644 transactions/__init__.py delete mode 100644 transactions/admin.py delete mode 100644 transactions/apps.py delete mode 100644 transactions/controllers/update_transactions_controller.py delete mode 100644 transactions/management/commands/fetch_recent_transactions.py delete mode 100644 transactions/management/commands/ingest_log_transactions.py delete mode 100644 transactions/migrations/0001_initial.py delete mode 100644 transactions/migrations/__init__.py delete mode 100644 transactions/models.py delete mode 100644 transactions/tests.py delete mode 100644 transactions/views.py delete mode 100644 util/constants.py delete mode 100644 util/time.py diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a711a3c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.9 + +WORKDIR /src +COPY requirements.txt /src/ +RUN pip install -r requirements.txt +COPY . /src/ \ No newline at end of file diff --git a/api/__init__.py b/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/admin.py b/api/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/api/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/api/apps.py b/api/apps.py deleted file mode 100644 index 878e7d5..0000000 --- a/api/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class ApiConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "api" diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py deleted file mode 100644 index 767987e..0000000 --- a/api/datatype/Eatery.py +++ /dev/null @@ -1,143 +0,0 @@ -from datetime import date -from typing import Optional -from enum import Enum - -import pytz -from api.datatype.EateryAlert import EateryAlert - -from api.datatype.Event import Event, filter_range -from api.datatype.WaitTimesDay import WaitTimesDay - - -class EateryID(Enum): - ONE_ZERO_FOUR_WEST = 1 - LIBE_CAFE = 2 - ATRIUM_CAFE = 3 - BEAR_NECESSITIES = 4 - BECKER_HOUSE = 5 - BIG_RED_BARN = 6 - BUS_STOP_BAGELS = 7 - CAFE_JENNIE = 8 - CAROLS_CAFE = 9 - COOK_HOUSE = 10 - DAIRY_BAR = 11 - CROSSINGS_CAFE = 12 - FRANNYS = 13 - GOLDIES_CAFE = 14 - GREEN_DRAGON = 15 - HOT_DOG_CART = 16 - ICE_CREAM_BIKE = 17 - BETHE_HOUSE = 18 - JANSENS_MARKET = 19 - KEETON_HOUSE = 20 - MANN_CAFE = 21 - MARTHAS_CAFE = 22 - MATTINS_CAFE = 23 - MCCORMICKS = 24 - NORTH_STAR_DINING = 25 - OKENSHIELDS = 26 - RISLEY = 27 - RPCC = 28 - ROSE_HOUSE = 29 - RUSTYS = 30 - STRAIGHT_FROM_THE_MARKET = 31 - TRILLIUM = 32 - TERRACE = 33 - MACS_CAFE = 34 - TEMPLE_OF_ZEUS = 35 - GIMME_COFFEE = 36 - LOUIES = 37 - ANABELS_GROCERY = 38 - -class Eatery: - - def __init__( - self, - id: EateryID, - name: Optional[str] = None, - image_url: Optional[str] = None, - menu_summary: Optional[str] = None, - campus_area: Optional[str] = None, - events: Optional[list[Event]] = None, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - payment_methods: Optional[list[str]] = None, - location: Optional[str] = None, - online_order: Optional[bool] = None, - online_order_url: Optional[str] = None, - wait_times: Optional[list[WaitTimesDay]] = None, - alerts: Optional[list[EateryAlert]] = None - ): - self.id = id - self.name = name - self.image_url = image_url - self.menu_summary = menu_summary - self.campus_area = campus_area - self.latitude = latitude - self.longitude = longitude - self.known_events = events - self.payment_methods = payment_methods - self.location = location - self.online_order = online_order - self.online_order_url = online_order_url - self.wait_times = wait_times - self.alerts = alerts - - def events( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None, - ): - return filter_range(self.known_events, tzinfo, start, end) - - def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None - ): - eatery = { - "id": self.id.value, - "name": self.name, - "image_url": self.image_url, - "menu_summary": self.menu_summary, - "campus_area": self.campus_area, - "events": None if self.known_events is None - else [event.to_json() for event in self.events(tzinfo, start, end)], - "latitude": self.latitude, - "longitude": self.longitude, - "payment_methods": None if self.payment_methods is None - else [payment_method for payment_method in self.payment_methods], - "location": self.location, - "online_order": self.online_order, - "online_order_url": self.online_order_url, - "wait_times": None if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times], - "alerts": None if self.alerts is None else [alert.to_json() for alert in self.alerts] - } - return eatery - - @staticmethod - def from_json(eatery_json): - return Eatery( - id=None if "id" not in eatery_json else EateryID(eatery_json["id"]), - name=eatery_json.get("name"), - image_url=eatery_json.get("image_url"), - menu_summary=eatery_json.get("menu_summary"), - campus_area=eatery_json.get("campus_area"), - events=None if "events" not in eatery_json or eatery_json["events"] is None - else [Event.from_json(event) for event in eatery_json["events"]], - latitude=eatery_json.get("latitude"), - longitude=eatery_json.get("longitude"), - payment_methods=eatery_json.get("payment_methods"), - location=eatery_json.get("location"), - online_order=eatery_json.get("online_order"), - online_order_url=eatery_json.get("online_order_url"), - wait_times=None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None - else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]], - alerts=None if "alerts" not in eatery_json or eatery_json["alerts"] is None - else [EateryAlert.from_json(alert) for alert in eatery_json["alerts"]] - ) - - def clone(self): - return Eatery.from_json(self.to_json()) diff --git a/api/datatype/EateryAlert.py b/api/datatype/EateryAlert.py deleted file mode 100644 index 2bb511b..0000000 --- a/api/datatype/EateryAlert.py +++ /dev/null @@ -1,30 +0,0 @@ -class EateryAlert: - - def __init__( - self, - id: int, - description: str, - start_timestamp: int, - end_timestamp: int - ): - self.id = id - self.description = description - self.start_timestamp = start_timestamp - self.end_timestamp = end_timestamp - - def to_json(self): - return { - "id": 1, - "description": self.description, - "start_timestamp": self.start_timestamp, - "end_timestamp": self.end_timestamp - } - - @staticmethod - def from_json(alert_json): - return EateryAlert( - id = alert_json["id"], - description=alert_json["description"], - start_timestamp=alert_json["start_timestamp"], - end_timestamp=alert_json["end_timestamp"] - ) diff --git a/api/datatype/Event.py b/api/datatype/Event.py deleted file mode 100644 index 9d3afcf..0000000 --- a/api/datatype/Event.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Optional -from datetime import date, time -from api.datatype.Menu import Menu -from util.time import combined_timestamp - -import pytz - - -class Event: - - def __init__( - self, - description: str, - canonical_date: date, - start_timestamp: int, - end_timestamp: int, - menu: Menu - ): - self.description = description - self.canonical_date = canonical_date - self.start_timestamp = start_timestamp - self.end_timestamp = end_timestamp - self.menu = menu - - def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None - ): - return { - "description": self.description, - "canonical_date": str(self.canonical_date), - "start_timestamp": self.start_timestamp, - "end_timestamp": self.end_timestamp, - "menu": self.menu.to_json() - } - - @staticmethod - def from_json(event_json): - return Event( - description=event_json["description"], - canonical_date=date.fromisoformat(event_json["canonical_date"]), - start_timestamp=event_json["start_timestamp"], - end_timestamp=event_json["end_timestamp"], - menu=Menu.from_json(event_json["menu"]) - ) - - def __contains__(self, item: int): - return self.start_timestamp <= item <= self.end_timestamp - -def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): - if events is None: - return [] - - if start is None and end is None: - return events - - elif tzinfo is not None and start is not None and end is None: - start_ts = combined_timestamp(start, time(), tzinfo) - return [event for event in events if ( - (start_ts in event) or start == event.canonical_date - )] - - elif tzinfo is not None and start is not None and end is not None: - start_ts = combined_timestamp(start, time(), tzinfo) - end_ts = combined_timestamp(end, time(), tzinfo) - return [event for event in events if ( - (start_ts in event) or (end_ts in event) or start <= event.canonical_date <= end - )] - - else: - raise Exception(f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}") \ No newline at end of file diff --git a/api/datatype/Menu.py b/api/datatype/Menu.py deleted file mode 100644 index c48e100..0000000 --- a/api/datatype/Menu.py +++ /dev/null @@ -1,15 +0,0 @@ -from api.datatype.MenuCategory import MenuCategory - -class Menu: - - def __init__(self, categories: list[MenuCategory]): - self.categories = categories - - def to_json(self): - return [category.to_json() for category in self.categories] - - @staticmethod - def from_json(menu_json): - return Menu( - categories=[MenuCategory.from_json(category) for category in menu_json] - ) diff --git a/api/datatype/MenuCategory.py b/api/datatype/MenuCategory.py deleted file mode 100644 index f2cd1cc..0000000 --- a/api/datatype/MenuCategory.py +++ /dev/null @@ -1,21 +0,0 @@ -from api.datatype.MenuItem import MenuItem - - -class MenuCategory: - - def __init__(self, category: str, items: list[MenuItem]): - self.category = category - self.items = items - - def to_json(self): - return { - "category": self.category, - "items": [item.to_json() for item in self.items] - } - - @staticmethod - def from_json(category_json): - return MenuCategory( - category=category_json["category"], - items=[MenuItem.from_json(item) for item in category_json["items"]] - ) diff --git a/api/datatype/MenuItem.py b/api/datatype/MenuItem.py deleted file mode 100644 index b611652..0000000 --- a/api/datatype/MenuItem.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Optional - -from api.datatype.MenuItemSection import MenuItemSection - -class MenuItem: - - @staticmethod - def from_cornell_dining_json(json_item: dict): - return MenuItem( - healthy=json_item["healthy"], - name=json_item["item"] - ) - - def __init__( - self, - name: str, - healthy: Optional[bool] = None, - base_price: Optional[float] = None, - description: Optional[str] = None, - sections: Optional[MenuItemSection] = None - ): - self.healthy = healthy - self.name = name - self.base_price = base_price - self.description = description - self.sections = sections - - def to_json(self): - return { - "healthy": self.healthy, - "name": self.name, - "base_price": self.base_price, - "description": self.description, - "sections": None if self.sections is None else [section.to_json() for section in self.sections] - } - - @staticmethod - def from_json(item_json): - return MenuItem( - name=item_json["name"], - healthy=item_json["healthy"], - base_price=item_json["base_price"], - description=item_json["description"], - sections=None if "sections" not in item_json or item_json["sections"] is None - else [MenuItemSection.from_json(section) for section in item_json["sections"]] - ) diff --git a/api/datatype/MenuItemSection.py b/api/datatype/MenuItemSection.py deleted file mode 100644 index 770c376..0000000 --- a/api/datatype/MenuItemSection.py +++ /dev/null @@ -1,20 +0,0 @@ -from api.datatype.MenuSubItem import MenuSubItem - -class MenuItemSection: - - def __init__(self, name: str, subitems: list[MenuSubItem]): - self.name = name - self.subitems = subitems - - def to_json(self): - return { - "name": self.name, - "subitems": [item.to_json() for item in self.subitems] - } - - @staticmethod - def from_json(section_json): - return MenuItemSection( - name=section_json["name"], - subitems=[MenuSubItem.from_json(item) for item in section_json["subitems"]] - ) diff --git a/api/datatype/MenuSubItem.py b/api/datatype/MenuSubItem.py deleted file mode 100644 index 854b0ed..0000000 --- a/api/datatype/MenuSubItem.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Optional - -class MenuSubItem: - - def __init__( - self, - name: str, - total_price: Optional[float], - additional_price: Optional[float] - ): - self.name = name - self.total_price = total_price - self.additional_price = additional_price - - def to_json(self): - return { - "name": self.name, - "total_price": self.total_price, - "additional_price": self.additional_price - } - - @staticmethod - def from_json(item_json): - return MenuSubItem( - name=item_json["name"], - total_price=item_json.get("total_price"), - additional_price=item_json.get("additional_price") - ) diff --git a/api/datatype/WaitTime.py b/api/datatype/WaitTime.py deleted file mode 100644 index 7e5cfca..0000000 --- a/api/datatype/WaitTime.py +++ /dev/null @@ -1,29 +0,0 @@ -class WaitTime: - def __init__( - self, - timestamp: int, - wait_time_low: float, - wait_time_expected: float, - wait_time_high: float - ): - self.timestamp = timestamp - self.wait_time_low = wait_time_low - self.wait_time_expected = wait_time_expected - self.wait_time_high = wait_time_high - - def to_json(self): - return { - "timestamp": self.timestamp, - "wait_time_low": self.wait_time_low, - "wait_time_expected": self.wait_time_expected, - "wait_time_high": self.wait_time_high - } - - @staticmethod - def from_json(wait_time_json): - return WaitTime( - timestamp=wait_time_json["timestamp"], - wait_time_low=wait_time_json["wait_time_low"], - wait_time_expected=wait_time_json["wait_time_expected"], - wait_time_high=wait_time_json["wait_time_high"] - ) diff --git a/api/datatype/WaitTimesDay.py b/api/datatype/WaitTimesDay.py deleted file mode 100644 index cb9ea86..0000000 --- a/api/datatype/WaitTimesDay.py +++ /dev/null @@ -1,25 +0,0 @@ -from datetime import date -from .WaitTime import WaitTime - - -class WaitTimesDay: - def __init__( - self, - canonical_date: date, - data: list[WaitTime] - ): - self.canonical_date = canonical_date - self.data = data - - def to_json(self): - return { - "canonical_date": str(self.canonical_date), - "data": [wait_time.to_json() for wait_time in self.data] - } - - @staticmethod - def from_json(wait_times_day_json): - return WaitTimesDay( - canonical_date=date.fromisoformat(wait_times_day_json["canonical_date"]), - data=[WaitTime.from_json(wait_time) for wait_time in wait_times_day_json["data"]] - ) diff --git a/api/dfg/CornellDiningNow.py b/api/dfg/CornellDiningNow.py deleted file mode 100644 index de104d7..0000000 --- a/api/dfg/CornellDiningNow.py +++ /dev/null @@ -1,59 +0,0 @@ -import requests - -from api.dfg.DfgNode import DfgNode - -from api.datatype.Eatery import Eatery -from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL - -from datetime import date - -class CornellDiningNow(DfgNode): - - def __call__(self, *args, **kwargs) -> list[Eatery]: - try: - response = requests.get(CORNELL_DINING_URL).json() - - except Exception as e: - raise e - - if response["status"] == "success": - json_eateries = response["data"]["eateries"] - eateries = [] - for json_eatery in json_eateries: - eateries.append(CornellDiningNow.parse_eatery(json_eatery)) - return eateries - - else: - raise Exception(response["message"]) - - @staticmethod - def parse_eatery(json_eatery: dict) -> Eatery: - # Events are parsed later - return Eatery( - id=dining_id_to_internal_id(json_eatery["id"]), - name=json_eatery["name"], - campus_area=json_eatery["campusArea"]["descrshort"], - latitude=json_eatery["latitude"], - longitude=json_eatery["longitude"], - payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), - location=json_eatery["location"], - online_order=json_eatery["onlineOrdering"], - online_order_url=json_eatery["onlineOrderUrl"] - ) - - @staticmethod - def generate_payment_methods(json_paymethods: list): - payment_methods = [] - takes_cash = True - takes_brbs = any([method["descrshort"] == "Meal Plan - Debit" for method in json_paymethods]) - takes_swipes = any([method["descrshort"] == "Meal Plan - Swipe" for method in json_paymethods]) - if takes_cash: - payment_methods.append("cash") - if takes_brbs: - payment_methods.append("brbs") - if takes_swipes: - payment_methods.append("swipes") - return payment_methods - - def description(self): - return "CornellDiningNow" diff --git a/api/dfg/DfgNode.py b/api/dfg/DfgNode.py deleted file mode 100644 index 57d5821..0000000 --- a/api/dfg/DfgNode.py +++ /dev/null @@ -1,10 +0,0 @@ -class DfgNode: - - def __call__(self, *args, **kwargs): - raise Exception() - - def children(self): - return [] - - def description(self): - raise Exception() diff --git a/api/dfg/EateriesFromDB.py b/api/dfg/EateriesFromDB.py deleted file mode 100644 index d474e66..0000000 --- a/api/dfg/EateriesFromDB.py +++ /dev/null @@ -1,80 +0,0 @@ -import datetime -import json -import re - -import pytz - -from api.datatype.Eatery import Eatery, EateryID -from api.datatype.EateryAlert import EateryAlert -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem -from api.dfg.DfgNode import DfgNode - -from eateries.models import EateryStore, AlertStore -from eateries.serializers import EateryStoreSerializer, AlertStoreSerializer - -from datetime import datetime - -# eventually need to deprecate this for a custom DB backend storing all of the overrides - -class EateriesFromDB(DfgNode): - - def __call__(self, *args, **kwargs) -> list[Eatery]: - eateries = EateryStore.objects.all() - serialized_eateries = EateryStoreSerializer(data=eateries, many=True) - serialized_eateries.is_valid() - alerts = AlertStore.objects.filter(end_timestamp__gte=datetime.now().timestamp(), start_timestamp__lte=datetime.now().timestamp()) - serialized_alerts = AlertStoreSerializer(data=alerts, many=True) - serialized_alerts.is_valid() - return list(map(lambda x: EateriesFromDB.eatery_from_serialized(x, serialized_alerts.data), serialized_eateries.data)) - - @staticmethod - def none_repr(str): - return None if str == None or len(str) == 0 else str - - @staticmethod - def eatery_from_serialized(serialized_eatery: dict, serialized_alerts: list[dict]) -> Eatery: - return Eatery( - id=EateryID(serialized_eatery["id"]), - name=EateriesFromDB.none_repr(serialized_eatery["name"]), - image_url=EateriesFromDB.none_repr(serialized_eatery["image_url"]), - menu_summary=EateriesFromDB.none_repr(serialized_eatery["menu_summary"]), - campus_area=EateriesFromDB.none_repr(serialized_eatery["campus_area"]), - events=None, - latitude=serialized_eatery["latitude"], - longitude=serialized_eatery["longitude"], - payment_methods=EateriesFromDB.payment_methods(serialized_eatery), - location=EateriesFromDB.none_repr(serialized_eatery["location"]), - online_order=serialized_eatery["online_order"], - online_order_url=EateriesFromDB.none_repr(serialized_eatery["online_order_url"]), - alerts = EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts) - ) - - @staticmethod - def payment_methods(serialized_eatery:dict): - pay_methods = [] - if serialized_eatery["payment_accepts_cash"]: - pay_methods.append("cash") - if serialized_eatery["payment_accepts_brbs"]: - pay_methods.append("brbs") - if serialized_eatery["payment_accepts_meal_swipes"]: - pay_methods.append("swipes") - return pay_methods - - @staticmethod - def alerts(eatery_id: int, serialized_alerts: list[dict]): - return [EateriesFromDB.alert_from_serialized(alert) for alert in serialized_alerts if alert["eatery"] == eatery_id] - - @staticmethod - def alert_from_serialized(serialized_alert: dict): - return EateryAlert( - id=serialized_alert["id"], - description=serialized_alert["description"], - start_timestamp=serialized_alert["start_timestamp"], - end_timestamp=serialized_alert["end_timestamp"] - ) - - - diff --git a/api/dfg/EateryStubs.py b/api/dfg/EateryStubs.py deleted file mode 100644 index 4b1d4c6..0000000 --- a/api/dfg/EateryStubs.py +++ /dev/null @@ -1,11 +0,0 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID - - -class EateryStubs(DfgNode): - - def __call__(self, *args, **kwargs) -> list[Eatery]: - return [Eatery(id=id) for id in EateryID] - - def description(self): - return "EateryStubs" diff --git a/api/dfg/__init__.py b/api/dfg/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/dfg/macros/EateryEvents.py b/api/dfg/macros/EateryEvents.py deleted file mode 100644 index 26c7079..0000000 --- a/api/dfg/macros/EateryEvents.py +++ /dev/null @@ -1,39 +0,0 @@ -from api.dfg.DfgNode import DfgNode - -from api.dfg.schedule.ClosedSchedule import ClosedSchedule -from api.dfg.schedule.DayOfWeekSchedule import DayOfWeekSchedule -from api.dfg.schedule.DateSchedule import DateSchedule -from api.dfg.schedule.CornellDiningEvents import CornellDiningEvents - -from api.dfg.macros.LeftMergeEvents import LeftMergeEvents - -from api.datatype.Eatery import EateryID -from api.dfg.schedule.CacheMenuInjection import CacheMenuInjection - -# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if -# conflict) -class EateryEvents(DfgNode): - def __init__(self, eatery_id: EateryID, cache): - self.macro = CacheMenuInjection( - ClosedSchedule( - eatery_id, - LeftMergeEvents( - DateSchedule(eatery_id, cache), - LeftMergeEvents( - DayOfWeekSchedule(eatery_id, cache), - CornellDiningEvents(eatery_id, cache) - ) - ), - cache - ), - cache - ) - - def children(self): - return self.macro.children() - - def __call__(self, *args, **kwargs): - return self.macro(*args, **kwargs) - - def description(self): - return "LeftMergeEvents" \ No newline at end of file diff --git a/api/dfg/macros/LeftMergeEateries.py b/api/dfg/macros/LeftMergeEateries.py deleted file mode 100644 index 92f5e87..0000000 --- a/api/dfg/macros/LeftMergeEateries.py +++ /dev/null @@ -1,31 +0,0 @@ -from api.dfg.DfgNode import DfgNode -from api.dfg.system.ConvertToJson import ConvertToJson -from api.dfg.system.ConvertFromJson import EateryFromJson -from api.dfg.system.LeftMerge import LeftMerge - -class LeftMergeEateries(DfgNode): - - def __init__(self, left: DfgNode, right: DfgNode): - def comparator(left, right): - if left["id"] == right["id"]: - return 0 - elif left["id"] < right["id"]: - return -1 - else: - return 1 - self.macro = EateryFromJson( - LeftMerge( - ConvertToJson(left), - ConvertToJson(right), - comparator - ) - ) - - def children(self): - return self.macro.children() - - def __call__(self, *args, **kwargs): - return self.macro(*args, **kwargs) - - def description(self): - return "LeftMergeEateries" diff --git a/api/dfg/macros/LeftMergeEvents.py b/api/dfg/macros/LeftMergeEvents.py deleted file mode 100644 index 264f940..0000000 --- a/api/dfg/macros/LeftMergeEvents.py +++ /dev/null @@ -1,35 +0,0 @@ -from api.dfg.DfgNode import DfgNode - -from api.dfg.system.ConvertToJson import ConvertToJson -from api.dfg.system.ConvertFromJson import EventFromJson -from api.dfg.system.LeftMerge import LeftMerge - -# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if -# conflict) -class LeftMergeEvents(DfgNode): - def __init__(self, left: DfgNode, right: DfgNode): - def comparator(left, right): - left_val = (left["canonical_date"], left["description"]) - right_val = (right["canonical_date"], right["description"]) - if left_val == right_val: - return 0 - elif left_val < right_val: - return -1 - else: - return 1 - self.macro = EventFromJson( - LeftMerge( - ConvertToJson(left), - ConvertToJson(right), - comparator - ) - ) - - def children(self): - return self.macro.children() - - def __call__(self, *args, **kwargs): - return self.macro(*args, **kwargs) - - def description(self): - return "LeftMergeEvents" \ No newline at end of file diff --git a/api/dfg/schedule/CacheMenuInjection.py b/api/dfg/schedule/CacheMenuInjection.py deleted file mode 100644 index a339102..0000000 --- a/api/dfg/schedule/CacheMenuInjection.py +++ /dev/null @@ -1,87 +0,0 @@ -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem -from api.datatype.MenuItemSection import MenuItemSection -from api.datatype.MenuSubItem import MenuSubItem -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID -from eateries.models import CategoryItemAssociation, SubItemStore - -class CacheMenuInjection(DfgNode): - - def __init__(self, child: DfgNode, cache): - self.cache = cache - self.child = child - - def __call__(self, *args, **kwargs) -> list[Eatery]: - if "menus" not in self.cache: - associations = CategoryItemAssociation.objects \ - .select_related("item") \ - .select_related("category") \ - .select_related("category__menu") \ - .all() - subitems = SubItemStore.objects.all() - - eatery_menus_categories_map = {} - item_subitem_map = {} - for subitem in subitems: - item_id = subitem.item_id - item_subsection = subitem.item_subsection - if item_id not in item_subitem_map: - item_subitem_map[item_id] = {} - if item_subsection not in item_subitem_map[item_id]: - item_subitem_map[item_id][item_subsection] = [] - item_subitem_map[item_id][item_subsection].append( - MenuSubItem( - name=subitem.name, - additional_price=subitem.additional_price, - total_price=subitem.total_price - ) - ) - - for association in associations: - eatery_id = EateryID(association.category.menu.eatery_id) - menu_id = association.category.menu_id - category = association.category.category - if eatery_id not in eatery_menus_categories_map: - eatery_menus_categories_map[eatery_id] = {} - if menu_id not in eatery_menus_categories_map[eatery_id]: - eatery_menus_categories_map[eatery_id][menu_id] = {} - if category not in eatery_menus_categories_map[eatery_id][menu_id]: - eatery_menus_categories_map[eatery_id][menu_id][category] = [] - item_sections = None - if association.item.id in item_subitem_map: - item_sections = [] - for section in item_subitem_map[association.item.id]: - item_sections.append(MenuItemSection(section, item_subitem_map[association.item.id][section])) - eatery_menus_categories_map[eatery_id][menu_id][category].append( - MenuItem( - name = association.item.name, - healthy = None, - base_price = association.item.base_price, - description = association.item.description, - sections = item_sections - ) - ) - eatery_menus_map = {} - for eatery_id in eatery_menus_categories_map: - eatery_menus_map[eatery_id] = {} - for menu_id in eatery_menus_categories_map[eatery_id]: - categories = [] - for category in eatery_menus_categories_map[eatery_id][menu_id]: - categories.append( - MenuCategory( - category=category, - items = eatery_menus_categories_map[eatery_id][menu_id][category] - ) - ) - eatery_menus_map[eatery_id][menu_id] = Menu( - categories=categories - ) - self.cache["menus"] = eatery_menus_map - - # self.cache["menus"] is a map of form {[eatery_id]: {[menu_id]: [Menu]}} - return self.child(*args, **kwargs) - - def description(self): - return "EateryStubs" diff --git a/api/dfg/schedule/ClosedSchedule.py b/api/dfg/schedule/ClosedSchedule.py deleted file mode 100644 index 31d4fc2..0000000 --- a/api/dfg/schedule/ClosedSchedule.py +++ /dev/null @@ -1,19 +0,0 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID - -class ClosedSchedule(DfgNode): - - def __init__(self, eatery_id: EateryID, child: DfgNode, cache): - self.eatery_id = eatery_id - self.child = child - self.cache = cache - - def __call__(self, *args, **kwargs) -> list[Eatery]: - # ClosedEventSchedule.objects.all() - return self.child(*args, **kwargs) - - def children(self): - return [self.child] - - def description(self): - return "ClosedSchedule" diff --git a/api/dfg/schedule/CornellDiningEvents.py b/api/dfg/schedule/CornellDiningEvents.py deleted file mode 100644 index 9bfc206..0000000 --- a/api/dfg/schedule/CornellDiningEvents.py +++ /dev/null @@ -1,113 +0,0 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem -from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL - -from datetime import date -import requests - -class CornellDiningEvents(DfgNode): - - def __init__(self, eatery_id: EateryID, cache): - self.eatery_id = eatery_id - self.cache = cache - - def __call__(self, *args, **kwargs) -> list[Eatery]: - if "eateries" not in self.cache: - try: - response = requests.get(CORNELL_DINING_URL).json() - except Exception as e: - raise e - if response["status"] == "success": - json_eateries = response["data"]["eateries"] - eateries = [] - for json_eatery in json_eateries: - eateries.append(CornellDiningEvents.parse_eatery(json_eatery)) - self.cache["eateries"] = eateries - for eatery in self.cache["eateries"]: - if eatery.id == self.eatery_id: - return eatery.events() - return [] - - @staticmethod - def parse_eatery(json_eatery: dict) -> Eatery: - is_cafe = "Cafe" in { - eatery_type["descr"] - for eatery_type in json_eatery["eateryTypes"] - } - return Eatery( - id=dining_id_to_internal_id(json_eatery["id"]), - events=CornellDiningEvents.eatery_events_from_json( - json_operating_hours=json_eatery["operatingHours"], - json_dining_items=json_eatery["diningItems"], - is_cafe=is_cafe - ) - ) - @staticmethod - def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: - json_operating_hours = sorted( - json_operating_hours, - key=lambda json_date_events: json_date_events["date"] - ) - events = [] - - for json_date_events in json_operating_hours: - canonical_date = date.fromisoformat(json_date_events["date"]) - - for json_event in json_date_events["events"]: - events.append(Event( - canonical_date=canonical_date, - description=json_event["descr"], - start_timestamp=json_event["startTimestamp"], - end_timestamp=json_event["endTimestamp"], - menu=CornellDiningEvents.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe) - )) - - return events - - @staticmethod - def eatery_menu_from_json(json_menu: list, json_dining_items: list, is_cafe: bool): - if is_cafe: - return CornellDiningEvents.cafe_menu_from_json(json_dining_items) - else: - return CornellDiningEvents.dining_hall_menu_from_json(json_menu) - - @staticmethod - def cafe_menu_from_json(json_dining_items: list) -> Menu: - category_map = {} - for item in json_dining_items: - if item['category'] not in category_map: - category_map[item['category']] = [] - category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) - categories = [] - for category_name in category_map: - categories.append(MenuCategory(category_name, category_map[category_name])) - return Menu(categories=categories) - - @staticmethod - def dining_hall_menu_from_json(json_menu: list) -> Menu: - json_menu = sorted( - json_menu, - key=lambda json_menu_category: json_menu_category["sortIdx"] - ) - menu_categories = [] - - for json_menu_category in json_menu: - items = [ - MenuItem.from_cornell_dining_json(json_item) - for json_item in json_menu_category["items"] - ] - - menu_categories.append(MenuCategory( - category=json_menu_category["category"], - items=items - )) - - return Menu(categories=menu_categories) - - - def description(self): - return "CornellDiningEvents" \ No newline at end of file diff --git a/api/dfg/schedule/DateSchedule.py b/api/dfg/schedule/DateSchedule.py deleted file mode 100644 index 738eaa0..0000000 --- a/api/dfg/schedule/DateSchedule.py +++ /dev/null @@ -1,16 +0,0 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID -# from eateries.models import DateEventSchedule - -class DateSchedule(DfgNode): - - def __init__(self, eatery_id: EateryID, cache): - self.eatery_id = eatery_id - self.cache = cache - - def __call__(self, *args, **kwargs) -> list[Eatery]: - # DateEventSchedule.objects.all() - return [] - - def description(self): - return "EateryStubs" diff --git a/api/dfg/schedule/DayOfWeekSchedule.py b/api/dfg/schedule/DayOfWeekSchedule.py deleted file mode 100644 index 2966806..0000000 --- a/api/dfg/schedule/DayOfWeekSchedule.py +++ /dev/null @@ -1,37 +0,0 @@ - -from api.datatype.Event import Event -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID -from eateries.models import DayOfWeekEventSchedule -from datetime import timedelta, datetime -from util.time import combined_timestamp - -import pytz - -class DayOfWeekSchedule(DfgNode): - - def __init__(self, eatery_id: EateryID, cache): - self.eatery_id = eatery_id - self.cache = cache - - def __call__(self, *args, **kwargs) -> list[Eatery]: - if "day_of_week_schedules" not in self.cache: - self.cache["day_of_week_schedules"] = DayOfWeekEventSchedule.objects.all().values() - schedules = [sched for sched in self.cache["day_of_week_schedules"] if EateryID(sched["eatery_id"]) == self.eatery_id] - events = [] - date = kwargs.get("start") - while date <= kwargs.get("end"): - day_schedule = [sched for sched in schedules if sched["day_of_week"] == date.strftime("%A")] - for sched in day_schedule: - events.append(Event( - description=sched["event_description"], - canonical_date=date, - start_timestamp=combined_timestamp(date=date, time=sched["start"], tzinfo=kwargs.get("tzinfo")), - end_timestamp=combined_timestamp(date=date, time=sched["end"], tzinfo=kwargs.get("tzinfo")), - menu=self.cache["menus"][self.eatery_id][sched["menu_id"]] - )) - date += timedelta(days = 1) - return events - - def description(self): - return "DayOfWeekSchedule" diff --git a/api/dfg/system/ConvertFromJson.py b/api/dfg/system/ConvertFromJson.py deleted file mode 100644 index b85f28f..0000000 --- a/api/dfg/system/ConvertFromJson.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Union - -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery -from api.datatype.Event import Event - -class EateryFromJson(DfgNode): - - def __init__(self, child: DfgNode): - self.child = child - - def __call__(self, *args, **kwargs): - result = self.child(*args, **kwargs) - return EateryFromJson.from_json(result, *args, **kwargs) - - def children(self): - return [self.child] - - @staticmethod - def from_json(obj: Union[list, dict], *args, **kwargs): - if isinstance(obj, list): - return [ - EateryFromJson.from_json(elem, *args, **kwargs) - for elem in obj - ] - else: - return Eatery.from_json(obj) - - def description(self): - return "EateryFromJson" - -class EventFromJson(DfgNode): - - def __init__(self, child: DfgNode): - self.child = child - - def __call__(self, *args, **kwargs): - result = self.child(*args, **kwargs) - return EventFromJson.from_json(result, *args, **kwargs) - - def children(self): - return [self.child] - - @staticmethod - def from_json(obj: Union[list, dict], *args, **kwargs): - if isinstance(obj, list): - return [ - EventFromJson.from_json(elem, *args, **kwargs) - for elem in obj - ] - else: - return Event.from_json(obj) - - def description(self): - return "EventFromJson" diff --git a/api/dfg/system/ConvertToJson.py b/api/dfg/system/ConvertToJson.py deleted file mode 100644 index 25129f0..0000000 --- a/api/dfg/system/ConvertToJson.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Union -from api.datatype.Event import Event - -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery - -class ConvertToJson(DfgNode): - - def __init__(self, child: DfgNode): - self.child = child - - def __call__(self, *args, **kwargs): - result = self.child(*args, **kwargs) - return ConvertToJson.to_json(result, *args, **kwargs) - - def children(self): - return [self.child] - - @staticmethod - def to_json(obj: Union[list, Eatery, Event], *args, **kwargs): - if isinstance(obj, list): - return [ - ConvertToJson.to_json(elem, *args, **kwargs) - for elem in obj - ] - else: - return obj.to_json( - tzinfo=kwargs.get("tzinfo"), - start=kwargs.get("start"), - end=kwargs.get("end") - ) - - def description(self): - return "ConvertToJson" diff --git a/api/dfg/system/DictResponseWrapper.py b/api/dfg/system/DictResponseWrapper.py deleted file mode 100644 index 618d89b..0000000 --- a/api/dfg/system/DictResponseWrapper.py +++ /dev/null @@ -1,29 +0,0 @@ -from api.dfg.DfgNode import DfgNode - - -class DictResponseWrapper(DfgNode): - - def __init__(self, child: DfgNode, re_raise_exceptions: bool = False): - self.child = child - self.re_raise_exceptions = re_raise_exceptions - - def __call__(self, *args, **kwargs): - try: - return { - "success": True, - "data": self.child(*args, **kwargs), - "error": None - } - - except Exception as e: - if self.re_raise_exceptions: - raise e - - return { - "success": False, - "data": None, - "error": str(e) - } - - def description(self): - return "DictResponseWrapper" diff --git a/api/dfg/system/EateryGenerator.py b/api/dfg/system/EateryGenerator.py deleted file mode 100644 index 7cbb6dc..0000000 --- a/api/dfg/system/EateryGenerator.py +++ /dev/null @@ -1,25 +0,0 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID -from typing import Optional - -class EateryGenerator(DfgNode): - - def __init__( - self, - eatery_id: EateryID, - events_dfg: Optional[DfgNode] = None, - wait_times_dfg: Optional[DfgNode] = None - ): - self.eatery_id = eatery_id - self.events_dfg = events_dfg - self.wait_times_dfg = wait_times_dfg - - def __call__(self, *args, **kwargs) -> list: - return Eatery( - id = self.eatery_id, - events=None if self.events_dfg is None else self.events_dfg(*args, **kwargs), - wait_times=None if self.wait_times_dfg is None else self.wait_times_dfg(*args, **kwargs) - ) - - def description(self): - return "EateryGenerator" diff --git a/api/dfg/system/InMemoryCache.py b/api/dfg/system/InMemoryCache.py deleted file mode 100644 index afd6ea9..0000000 --- a/api/dfg/system/InMemoryCache.py +++ /dev/null @@ -1,75 +0,0 @@ -import time - -from api.dfg.DfgNode import DfgNode -from typing import Optional - -from api.dfg.system.ConvertToJson import ConvertToJson - - -class DataSnapshot: - - def __init__(self, args, kwargs, data, time): - self.data = data - self.recorded_time = time - self.args = args - self.kwargs = kwargs - - def is_usable_snapshot(self, oldest_possible_time, args, kwargs): - return args == self.args and kwargs == self.kwargs and self.recorded_time >= oldest_possible_time - - def get_data(self): - return self.data - - def to_json(self): - return ConvertToJson.to_json(self.data, *self.args, **self.kwargs) - - def get_recorded_time(self): - return self.recorded_time - - -class InMemoryCache(DfgNode): - - def __init__(self, child, expiration: float = 3600, max_size: int = 5): - self.child = child - self.expiration = expiration - self.max_size = max_size - self.snapshots: list[DataSnapshot] = [] - - def current_time(self): - return time.time() - - def fifo_index(self): - if len(self.snapshots) == 0: - return None - oldest_snapshot_time = self.snapshots[0].get_recorded_time() - oldest_snapshot_index = 0 - for i in range(1, len(self.snapshots)): - if self.snapshots[i].get_recorded_time() < oldest_snapshot_time: - oldest_snapshot_time = self.snapshots[i].get_recorded_time() - oldest_snapshot_index = i - return oldest_snapshot_index - - def __call__(self, *args, **kwargs): - should_reload = kwargs.get("reload") - for snapshot in self.snapshots: - if not should_reload and snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): - # print(f"{self}: Returning from cache") - return snapshot.get_data() - - # print(f"{self}: Fetching data") - new_snapshot = DataSnapshot(args, kwargs, self.child(*args, **kwargs), self.current_time()) - if len(self.snapshots) < self.max_size: - self.snapshots.append(new_snapshot) - else: - index_to_replace = self.fifo_index() - self.snapshots[index_to_replace] = new_snapshot - return new_snapshot.get_data() - - def to_json(self, *args, **kwargs): - for snapshot in self.snapshots: - if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): - return snapshot.to_json() - return ConvertToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) - - def description(self): - return "InMemoryCache" diff --git a/api/dfg/system/LeftMerge.py b/api/dfg/system/LeftMerge.py deleted file mode 100644 index 557daf1..0000000 --- a/api/dfg/system/LeftMerge.py +++ /dev/null @@ -1,54 +0,0 @@ -from api.dfg.DfgNode import DfgNode -from typing import Callable, TypeVar -from functools import cmp_to_key -T = TypeVar("T") - -# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if -# conflict) -class LeftMerge(DfgNode): - - def __init__(self, left: DfgNode, right: DfgNode, comparator: Callable[[T, T], int]): - self.left = left - self.right = right - self.comparator = comparator - - def children(self): - return [self.left, self.right] - - def __call__(self, *args, **kwargs): - left_lst = sorted(self.left(*args, **kwargs), key=cmp_to_key(self.comparator)) - right_lst = sorted(self.right(*args, **kwargs), key=cmp_to_key(self.comparator)) - left_json = _pop_first(left_lst) - right_json = _pop_first(right_lst) - merged_lst = [] - while left_json is not None and right_json is not None: - if self.comparator(left_json, right_json) == 0: - merged_json = {} - for key in right_json: - if right_json[key] is not None: - merged_json[key] = right_json[key] - for key in left_json: - if left_json[key] is not None: - merged_json[key] = left_json[key] - merged_lst.append(merged_json) - left_json = _pop_first(left_lst) - right_json = _pop_first(right_lst) - elif self.comparator(left_json, right_json) < 0: - merged_lst.append(left_json) - left_json = _pop_first(left_lst) - else: - merged_lst.append(right_json) - right_json = _pop_first(right_lst) - merged_lst.extend(left_lst) - merged_lst.extend(right_lst) - return merged_lst - - def description(self): - return "LeftMerge" - - -def _pop_first(lst: list): - try: - return lst.pop(0) - except IndexError: - return None diff --git a/api/dfg/system/Mapping.py b/api/dfg/system/Mapping.py deleted file mode 100644 index 95539e3..0000000 --- a/api/dfg/system/Mapping.py +++ /dev/null @@ -1,20 +0,0 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID -from typing import Callable, Any - -class Mapping(DfgNode): - - def __init__(self, child: DfgNode, fn: Callable[[Any, dict], DfgNode]): - self.child = child - self.fn = fn - - def __call__(self, *args, **kwargs) -> list: - result = [] - cache = {} - for ele in self.child(*args, **kwargs): - dfg = self.fn(ele, cache) - result.append(dfg(*args, **kwargs)) - return result - - def description(self): - return "Mapping" diff --git a/api/dfg/wait_times/WaitTimeFilter.py b/api/dfg/wait_times/WaitTimeFilter.py deleted file mode 100644 index ae2bac0..0000000 --- a/api/dfg/wait_times/WaitTimeFilter.py +++ /dev/null @@ -1,38 +0,0 @@ -from api.datatype.WaitTimesDay import WaitTimesDay -from api.dfg.DfgNode import DfgNode - - -# Removes all wait times that are not part of the eatery's events - -class WaitTimeFilter(DfgNode): - def __init__(self, child: DfgNode): - self.child = child - - def children(self): - return [self.child] - - def __call__(self, *args, **kwargs): - eateries = self.child(*args, **kwargs) - result = [] - for eatery in eateries: - if eatery.wait_times is None: - result.append(eatery.clone()) - else: - wait_times_filtered = [] - for day_wait_times in eatery.wait_times: - filtered_data = [] - for wait_time_data in day_wait_times.data: - eatery_events = eatery.events() - if any([wait_time_data.timestamp in event for event in eatery_events]): - filtered_data.append(wait_time_data) - wait_times_filtered.append(WaitTimesDay( - canonical_date=day_wait_times.canonical_date, - data=filtered_data - )) - eatery_clone = eatery.clone() - eatery_clone.wait_times = wait_times_filtered - result.append(eatery_clone) - return result - - def description(self): - return "WaitTimeFilter" diff --git a/api/dfg/wait_times/WaitTimes.py b/api/dfg/wait_times/WaitTimes.py deleted file mode 100644 index 369a9e6..0000000 --- a/api/dfg/wait_times/WaitTimes.py +++ /dev/null @@ -1,115 +0,0 @@ -from datetime import date, timedelta - -import pytz -from django.db.models import Avg - -from api.datatype.Eatery import Eatery, EateryID -from api.datatype.Event import Event -from api.datatype.WaitTime import WaitTime -from api.datatype.WaitTimesDay import WaitTimesDay -from api.dfg.DfgNode import DfgNode -from transactions.models import TransactionHistory - -from util.time import combined_timestamp - -class WaitTimes(DfgNode): - - def __init__(self, eatery_id: EateryID, cache): - self.eatery_id = eatery_id - self.cache = cache - - def __call__(self, *args, **kwargs) -> list[Eatery]: - if "transactions" not in self.cache: - transactions = {} - date = kwargs.get("start") - while date <= kwargs.get("end"): - transactions[date] = [] - past_days = [] - for i in range(1, 13): - past_days.append(date - timedelta(days=7*i)) - transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ - .values("eatery_id", "block_end_time") \ - .annotate(transaction_avg=Avg("transaction_count")) - for unit in transaction_avg_counts: - transactions[date].append(unit) - date += timedelta(days=1) - self.cache["transactions"] = transactions - - eatery_wait_times = [] - for date in self.cache["transactions"]: - eatery_transaction_avgs = [transaction_avg for transaction_avg in self.cache["transactions"][date] if transaction_avg["eatery_id"] == self.eatery_id.value] - eatery_wait_times.append(WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs, kwargs.get("tzinfo"))) - - return eatery_wait_times - - # Expected amount of time (in seconds) for the length of the line to decrease by 1 person - # Returns [lower, expected, upper] - @staticmethod - def line_decrease_by_one_time(eatery_id: EateryID) -> list[int]: - if eatery_id == EateryID.MACS_CAFE: - return [24, 27, 30] - elif eatery_id == EateryID.MATTINS_CAFE: - return [9, 15, 21] - elif eatery_id == EateryID.TERRACE: - return [15, 27, 36] - elif eatery_id == EateryID.OKENSHIELDS: - return [4, 8, 12] - else: - return [18, 21, 24] - - # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the - # amount of time to check out Returns [lower, expected, upper] - @staticmethod - def base_time_to_get_food(eatery_id: EateryID) -> list[int]: - if eatery_id == EateryID.MACS_CAFE: - return [240, 300, 360] - elif eatery_id == EateryID.MATTINS_CAFE: - return [150, 210, 270] - elif eatery_id == EateryID.TERRACE: - return [180, 300, 420] - elif eatery_id == EateryID.OKENSHIELDS: - return [80, 120, 180] - else: - return [180, 240, 300] - - @staticmethod - def generate_eatery_wait_times_by_day( - eatery_id: EateryID, - date: date, - transactions: list, - tzinfo: pytz.tzinfo - ) -> WaitTimesDay: - wait_times_data = [] - customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - for index in reversed(range(0, len(transactions))): - base_times = WaitTimes.base_time_to_get_food(eatery_id) - line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) - # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago - how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index]["transaction_avg"] - prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) - if prev_bucket_guest_arrival > 9: - pass - # TODO: Send a slack error here instead - # print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") - else: - customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index]["transaction_avg"] - num_customers = customers_waiting_in_line.pop(0) - wait_time_low = int(base_times[0] + line_decrease_times[0] * num_customers) - wait_time_expected = int(base_times[1] + line_decrease_times[1] * num_customers) - wait_time_high = int(base_times[2] + line_decrease_times[2] * num_customers) - - customers_waiting_in_line.append(0.0) - block_end_time = transactions[index]['block_end_time'] - timestamp = int(combined_timestamp(date, block_end_time, tzinfo) - 5 * 60 / 2) - wait_times_data.insert(0, WaitTime( - timestamp=timestamp, - wait_time_low=wait_time_low, - wait_time_expected=wait_time_expected, - wait_time_high=wait_time_high - )) - - return WaitTimesDay(canonical_date=date, data=wait_times_data) - - - def description(self): - return "WaitTimes" diff --git a/api/migrations/0001_initial.py b/api/migrations/0001_initial.py deleted file mode 100644 index c6f14d5..0000000 --- a/api/migrations/0001_initial.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.0 on 2021-12-23 19:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='TransactionHistory', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('canonical_date', models.DateField()), - ('start_timestamp', models.TimeField()), - ('end_timestamp', models.TimeField()), - ('transaction_count', models.IntegerField()), - ], - ), - ] diff --git a/api/migrations/__init__.py b/api/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/api/tests.py b/api/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/api/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/api/urls.py b/api/urls.py deleted file mode 100644 index 4bf0e28..0000000 --- a/api/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.urls import path - -from . import views - -urlpatterns = [ - path("", views.index, name="index"), - path("gs", views.google_sheets_eateries, name="gs") -] diff --git a/api/views.py b/api/views.py deleted file mode 100644 index e44d9af..0000000 --- a/api/views.py +++ /dev/null @@ -1,86 +0,0 @@ -from datetime import date, timedelta - -import pytz -from django.http import JsonResponse -from api.datatype.Eatery import Eatery - -from api.dfg.CornellDiningNow import CornellDiningNow -from api.dfg.EateryStubs import EateryStubs -from api.dfg.EateriesFromDB import EateriesFromDB -from api.dfg.macros.EateryEvents import EateryEvents - -from api.dfg.system.DictResponseWrapper import DictResponseWrapper -from api.dfg.system.ConvertToJson import ConvertToJson -from api.dfg.system.EateryGenerator import EateryGenerator -from api.dfg.system.InMemoryCache import InMemoryCache -from api.dfg.system.Mapping import Mapping - -from api.dfg.macros.LeftMergeEateries import LeftMergeEateries - -from api.dfg.wait_times.WaitTimes import WaitTimes -from api.dfg.wait_times.WaitTimeFilter import WaitTimeFilter - -main_dfg = DictResponseWrapper( - ConvertToJson( - InMemoryCache( - WaitTimeFilter( - LeftMergeEateries( - Mapping( - child=EateryStubs(), - fn = lambda eatery, cache: EateryGenerator( - eatery_id=eatery.id, - wait_times_dfg=WaitTimes(eatery.id, cache) - ) - ), - LeftMergeEateries( - Mapping( - child = EateryStubs(), - fn = lambda eatery, cache: EateryGenerator( - eatery_id=eatery.id, - events_dfg=EateryEvents(eatery.id, cache) - ) - ), - LeftMergeEateries( - EateriesFromDB(), - LeftMergeEateries( - CornellDiningNow(), - EateryStubs() - ) - ) - ) - ) - ) - ) - ), - re_raise_exceptions=True -) - - -def index(request): - tzinfo = pytz.timezone("US/Eastern") - reload = request.GET.get('reload') - result = main_dfg( - tzinfo=tzinfo, - reload=reload is not None and reload != "false", - start=date.today(), - end=date.today() + timedelta(days=7) - ) - return JsonResponse(result) - - -def google_sheets_eateries(request): - # dfg = DictResponseWrapper( - # EateryToJson( - # GoogleSheetsEateries( - # spreadsheet_id="1ImfeTUA6I1Ub-aavgIW53Pf7EVB694f1294NPSCRd5c" - # ) - # ) - # ) - - # result = dfg( - # tzinfo=pytz.timezone("US/Eastern"), - # start=date.today(), - # end=date.today() + timedelta(days=7) - # ) - - return JsonResponse([]) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..064fe6f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +services: + web: + build: . + env_file: .env + volumes: + - .:/src + web_make_migrations: + extends: + service: web + command: python manage.py makemigrations + web_migrate: + extends: + service: web + command: python manage.py migrate + web_run: + extends: + service: web + command: python manage.py runserver 0.0.0.0:8000 + ports: + - "8000:8000" \ No newline at end of file diff --git a/eateries/__init__.py b/eateries/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/eateries/admin.py b/eateries/admin.py deleted file mode 100644 index 1a1a610..0000000 --- a/eateries/admin.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.contrib import admin - -import eateries.models as models -# Register your models here. - -admin.site.register(models.EateryStore) -admin.site.register(models.MenuStore) -admin.site.register(models.ItemStore) -admin.site.register(models.SubItemStore) -admin.site.register(models.AlertStore) -admin.site.register(models.CategoryStore) -admin.site.register(models.CategoryItemAssociation) -admin.site.register(models.DayOfWeekEventSchedule) -admin.site.register(models.DateEventSchedule) -admin.site.register(models.ClosedEventSchedule) \ No newline at end of file diff --git a/eateries/apps.py b/eateries/apps.py deleted file mode 100644 index 6a490f8..0000000 --- a/eateries/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class EateriesConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'eateries' diff --git a/eateries/management/commands/create_db_snapshot.py b/eateries/management/commands/create_db_snapshot.py deleted file mode 100644 index 5d87397..0000000 --- a/eateries/management/commands/create_db_snapshot.py +++ /dev/null @@ -1,62 +0,0 @@ - -from django.core.management.base import BaseCommand - -from datetime import datetime -from pathlib import Path - -from util.constants import SnapshotFileName - -import eateries.models as models -import eateries.serializers as serializers -import pytz -import json - -class Command(BaseCommand): - help = 'Saves the current state of the database' - - def write_to_file(self, serializer, db_objects, folder_path, file_name_enum: SnapshotFileName): - file_path = f"{folder_path}/{file_name_enum.value}" - serialized_lst = serializer(db_objects, many=True) - with open(file_path, "w") as file: - for obj in serialized_lst.data: - file.write(json.dumps(obj) + "\n") - - def handle(self, *args, **options): - tzinfo = pytz.timezone("US/Eastern") - time = datetime.now(tzinfo).strftime("%Y-%m-%d %H:%M:%S") - folder_path = f"db_snapshots/{time}" - Path(folder_path).mkdir(parents=True, exist_ok=True) - - eateries = models.EateryStore.objects.all() - self.write_to_file(serializers.EateryStoreSerializer, eateries, folder_path, SnapshotFileName.EATERY_STORE) - - alerts = models.AlertStore.objects.filter(end_timestamp__gte=datetime.now().timestamp()) - self.write_to_file(serializers.AlertStoreSerializer, alerts, folder_path, SnapshotFileName.ALERT_STORE) - - menus = models.MenuStore.objects.all() - self.write_to_file(serializers.MenuStoreSerializer, menus, folder_path, SnapshotFileName.MENU_STORE) - - categories = models.CategoryStore.objects.all() - self.write_to_file(serializers.CategoryStoreSerializer, categories, folder_path, SnapshotFileName.CATEGORY_STORE) - - items = models.ItemStore.objects.all() - self.write_to_file(serializers.ItemStoreSerializer, items, folder_path, SnapshotFileName.ITEM_STORE) - - subitems = models.SubItemStore.objects.all() - self.write_to_file(serializers.SubItemStoreSerializer, subitems, folder_path, SnapshotFileName.SUBITEM_STORE) - - category_item_associations = models.CategoryItemAssociation.objects.all() - self.write_to_file(serializers.CategoryItemAssociationSerializer, category_item_associations, folder_path, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) - - # date_event_schedules = models.DateEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) - # self.write_to_file(date_event_schedules, f"{folder_path}/{SnapshotFileName.DATE_EVENT_SCHEDULE}") - - # closed_event_schedules = models.ClosedEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) - # self.write_to_file(closed_event_schedules, f"{folder_path}/{SnapshotFileName.CLOSED_EVENT_SCHEDULE}") - - day_of_week_event_schedules = models.DayOfWeekEventSchedule.objects.all() - self.write_to_file(serializers.DayOfWeekEventScheduleSerializer, day_of_week_event_schedules, folder_path, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) - - # # TODO: Need to filter here for only the valid schedules - # event_schedules = models.EventSchedule.objects.all() - # self.write_to_file(event_schedules, f"{folder_path}/{SnapshotFileName.EVENT_SCHEDULE}") \ No newline at end of file diff --git a/eateries/management/commands/ingest_db_snapshot.py b/eateries/management/commands/ingest_db_snapshot.py deleted file mode 100644 index ff7a8a0..0000000 --- a/eateries/management/commands/ingest_db_snapshot.py +++ /dev/null @@ -1,31 +0,0 @@ - -from django.core.management.base import BaseCommand -from util.constants import SnapshotFileName - -import eateries.serializers as serializers -import json - -class Command(BaseCommand): - help = 'Overrides current state of the db with a db snapshot' - - # Only writes data if the table has been flushed - def ingest_data(self, serializer, file_name: SnapshotFileName): - folder_path = "db_snapshots/2022-01-10 13:05:44" - with open(f"{folder_path}/{file_name.value}", "r") as file: - json_objs = [] - for line in file: - if (len(line) > 2): - json_objs.append(json.loads(line)) - serialized_objs = serializer(data=json~_objs, many=True) - serialized_objs.is_valid() - serialized_objs.save() - - def handle(self, *args, **options): - self.ingest_data(serializers.EateryStoreSerializer, SnapshotFileName.EATERY_STORE) - self.ingest_data(serializers.AlertStoreSerializer, SnapshotFileName.ALERT_STORE) - self.ingest_data(serializers.MenuStoreSerializer, SnapshotFileName.MENU_STORE) - self.ingest_data(serializers.CategoryStoreSerializer, SnapshotFileName.CATEGORY_STORE) - self.ingest_data(serializers.ItemStoreSerializer, SnapshotFileName.ITEM_STORE) - self.ingest_data(serializers.SubItemStoreSerializer, SnapshotFileName.SUBITEM_STORE) - self.ingest_data(serializers.CategoryItemAssociationSerializer, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) - self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) \ No newline at end of file diff --git a/eateries/migrations/0001_initial.py b/eateries/migrations/0001_initial.py deleted file mode 100644 index a571daa..0000000 --- a/eateries/migrations/0001_initial.py +++ /dev/null @@ -1,88 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 17:56 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='EateryStore', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=40)), - ('menu_summary', models.CharField(blank=True, max_length=60)), - ('image_url', models.URLField(blank=True)), - ('location', models.CharField(blank=True, max_length=30)), - ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), - ('online_order', models.BooleanField(blank=True, null=True)), - ('online_order_url', models.URLField(blank=True)), - ('latitude', models.FloatField(blank=True, null=True)), - ('longitude', models.FloatField(blank=True, null=True)), - ('payment_accepts_meal_swipes', models.BooleanField(blank=True, null=True)), - ('payment_accepts_brbs', models.BooleanField(blank=True, null=True)), - ('payment_accepts_cash', models.BooleanField(blank=True, null=True)), - ], - ), - migrations.CreateModel( - name='ItemStore', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=40)), - ('description', models.CharField(blank=True, max_length=200)), - ('base_price', models.FloatField(blank=True, null=True)), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - ), - migrations.CreateModel( - name='SubItemStore', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('additional_price', models.FloatField(blank=True, null=True)), - ('total_price', models.FloatField(blank=True, null=True)), - ('name', models.CharField(max_length=40)), - ('item_subsection', models.CharField(max_length=40)), - ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), - ], - ), - migrations.CreateModel( - name='MenuStore', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=40)), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - ), - migrations.CreateModel( - name='ExceptionStore', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('description', models.CharField(max_length=250)), - ('start_timestamp', models.IntegerField()), - ('end_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - ), - migrations.CreateModel( - name='CategoryStore', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('category', models.CharField(max_length=40)), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), - ], - ), - migrations.CreateModel( - name='CategoryItemAssociation', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.categorystore')), - ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), - ], - ), - ] diff --git a/eateries/migrations/0002_closedeventschedule_and_more.py b/eateries/migrations/0002_closedeventschedule_and_more.py deleted file mode 100644 index 358bf34..0000000 --- a/eateries/migrations/0002_closedeventschedule_and_more.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='ClosedEventSchedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('canonical_date', models.DateField()), - ], - options={ - 'abstract': False, - }, - ), - migrations.AlterUniqueTogether( - name='categoryitemassociation', - unique_together={('item', 'category')}, - ), - ] diff --git a/eateries/migrations/0003_closedeventschedule_eatery_and_more.py b/eateries/migrations/0003_closedeventschedule_eatery_and_more.py deleted file mode 100644 index b09825b..0000000 --- a/eateries/migrations/0003_closedeventschedule_eatery_and_more.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:19 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0002_closedeventschedule_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='closedeventschedule', - name='eatery', - field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore'), - preserve_default=False, - ), - migrations.AddField( - model_name='closedeventschedule', - name='event_description', - field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), - preserve_default=False, - ), - ] diff --git a/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py b/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py deleted file mode 100644 index de796ef..0000000 --- a/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:23 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0003_closedeventschedule_eatery_and_more'), - ] - - operations = [ - migrations.CreateModel( - name='DayOfWeekEventSchedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('day_of_week', models.CharField(choices=[('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday')], max_length=10)), - ('start', models.TimeField()), - ('end', models.TimeField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='DateEventSchedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('canonical_date', models.DateField()), - ('start_timestamp', models.IntegerField()), - ('end_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/eateries/migrations/0005_dateeventschedule_event_description_and_more.py b/eateries/migrations/0005_dateeventschedule_event_description_and_more.py deleted file mode 100644 index e6a89a3..0000000 --- a/eateries/migrations/0005_dateeventschedule_event_description_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:25 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0004_dayofweekeventschedule_dateeventschedule'), - ] - - operations = [ - migrations.AddField( - model_name='dateeventschedule', - name='event_description', - field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), - preserve_default=False, - ), - migrations.AddField( - model_name='dateeventschedule', - name='menu', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore'), - preserve_default=False, - ), - migrations.AddField( - model_name='dayofweekeventschedule', - name='event_description', - field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), - preserve_default=False, - ), - migrations.AddField( - model_name='dayofweekeventschedule', - name='menu', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore'), - preserve_default=False, - ), - ] diff --git a/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py b/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py deleted file mode 100644 index 0474536..0000000 --- a/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py +++ /dev/null @@ -1,70 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0005_dateeventschedule_event_description_and_more'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='categoryitemassociation', - unique_together=set(), - ), - migrations.AlterField( - model_name='categoryitemassociation', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='categorystore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='closedeventschedule', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='dateeventschedule', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='dayofweekeventschedule', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='exceptionstore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='itemstore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='menustore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='subitemstore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterUniqueTogether( - name='categorystore', - unique_together={('menu', 'category')}, - ), - migrations.AlterUniqueTogether( - name='menustore', - unique_together={('eatery', 'name')}, - ), - ] diff --git a/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py b/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py deleted file mode 100644 index e8d5d58..0000000 --- a/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.0 on 2022-01-11 13:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0006_alter_categoryitemassociation_unique_together_and_more'), - ] - - operations = [ - migrations.RenameModel( - old_name='ExceptionStore', - new_name='AlertStore', - ), - migrations.AlterUniqueTogether( - name='dayofweekeventschedule', - unique_together={('eatery', 'day_of_week', 'event_description')}, - ), - ] diff --git a/eateries/migrations/__init__.py b/eateries/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/eateries/models.py b/eateries/models.py deleted file mode 100644 index de25063..0000000 --- a/eateries/models.py +++ /dev/null @@ -1,111 +0,0 @@ -from django.db import models -from datetime import datetime -class EateryStore(models.Model): - class CampusArea(models.TextChoices): - WEST = 'West' - NORTH = 'North' - CENTRAL = 'Central' - COLLEGETOWN = 'Collegetown' - NONE = '' - - id = models.IntegerField(primary_key=True) - name = models.CharField(max_length=40, blank=True) - menu_summary = models.CharField(max_length = 60, blank=True) - image_url = models.URLField(blank=True) - location = models.CharField(max_length=30, blank=True) - campus_area = models.CharField(max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True) - online_order = models.BooleanField(null = True, blank=True) - online_order_url = models.URLField(blank=True) - latitude = models.FloatField(null = True, blank=True) - longitude = models.FloatField(null = True, blank=True) - payment_accepts_meal_swipes = models.BooleanField(null = True, blank=True) - payment_accepts_brbs = models.BooleanField(null = True, blank=True) - payment_accepts_cash = models.BooleanField(null = True, blank=True) - -class AlertStore(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - description = models.CharField(max_length = 250) - start_timestamp = models.IntegerField() - end_timestamp = models.IntegerField() - -class MenuStore(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - name = models.CharField(max_length = 40) - - class Meta: - unique_together = ('eatery', 'name') - -class CategoryStore(models.Model): - id = models.IntegerField(primary_key=True) - menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - category = models.CharField(max_length = 40) - - class Meta: - unique_together = ('menu', 'category') - -class ItemStore(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - name = models.CharField(max_length=40) - description = models.CharField(max_length = 200, blank=True) - base_price = models.FloatField(null = True, blank=True) - -class SubItemStore(models.Model): - id = models.IntegerField(primary_key=True) - item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) - additional_price = models.FloatField(null = True, blank=True) - total_price = models.FloatField(null = True, blank=True) - name = models.CharField(max_length=40) - item_subsection = models.CharField(max_length=40) - -class CategoryItemAssociation(models.Model): - id = models.IntegerField(primary_key=True) - item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) - category = models.ForeignKey(CategoryStore, on_delete=models.DO_NOTHING) -class EventDescription(models.TextChoices): - BREAKFAST = 'Breakfast' - BRUNCH = 'Brunch' - LUNCH = 'Lunch' - DINNER = 'Dinner' - GENERAL = 'General' - -class EventSchedule(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - class Meta: - abstract=True - -class DayOfWeekEventSchedule(EventSchedule): - - class DayOfTheWeek(models.TextChoices): - MONDAY = 'Monday' - TUESDAY = 'Tuesday' - WEDNESDAY = 'Wednesday' - THURSDAY = 'Thursday' - FRIDAY = 'Friday' - SATURDAY = 'Saturday' - SUNDAY = 'Sunday' - - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) - menu= models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=10) - start = models.TimeField() - end = models.TimeField() - class Meta: - unique_together = ('eatery', 'day_of_week', 'event_description') - - -class DateEventSchedule(EventSchedule): - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) - menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - canonical_date = models.DateField() - start_timestamp = models.IntegerField() - end_timestamp = models.IntegerField() - -class ClosedEventSchedule(EventSchedule): - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) - canonical_date = models.DateField() - - diff --git a/eateries/serializers.py b/eateries/serializers.py deleted file mode 100644 index 0dcc763..0000000 --- a/eateries/serializers.py +++ /dev/null @@ -1,43 +0,0 @@ -from rest_framework import serializers - -import eateries.models as models - -class EateryStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.EateryStore - fields = '__all__' - -class AlertStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.AlertStore - fields = '__all__' - -class MenuStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.MenuStore - fields = '__all__' - -class ItemStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.ItemStore - fields = '__all__' - -class SubItemStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.SubItemStore - fields = '__all__' - -class CategoryStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.CategoryStore - fields = '__all__' - -class CategoryItemAssociationSerializer(serializers.ModelSerializer): - class Meta: - model = models.CategoryItemAssociation - fields = '__all__' - -class DayOfWeekEventScheduleSerializer(serializers.ModelSerializer): - class Meta: - model = models.DayOfWeekEventSchedule - fields = '__all__' \ No newline at end of file diff --git a/eateries/tests.py b/eateries/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/eateries/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/eateries/views.py b/eateries/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/eateries/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/eatery_blue_backend/__init__.py b/eatery_blue_backend/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/eatery_blue_backend/asgi.py b/eatery_blue_backend/asgi.py deleted file mode 100644 index 87d9109..0000000 --- a/eatery_blue_backend/asgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -ASGI config for eatery_blue_backend project. - -It exposes the ASGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ -""" - -import os - -from django.core.asgi import get_asgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") - -application = get_asgi_application() diff --git a/eatery_blue_backend/settings.py b/eatery_blue_backend/settings.py deleted file mode 100644 index f63a119..0000000 --- a/eatery_blue_backend/settings.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -Django settings for eatery_blue_backend project. - -Generated by 'django-admin startproject' using Django 4.0. - -For more information on this file, see -https://docs.djangoproject.com/en/4.0/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/4.0/ref/settings/ -""" - -from pathlib import Path -import os - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-tup==8h@6!ewid!sfi*)jomsejj4j@=w=u*2ri9g0*0$3)1dkq" - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(",") - - -# Application definition - -INSTALLED_APPS = [ - "django.contrib.admin", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.sessions", - "django.contrib.messages", - "django.contrib.staticfiles", - "transactions", - "eateries", - "rest_framework" -] - -MIDDLEWARE = [ - "django.middleware.security.SecurityMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", -] - -ROOT_URLCONF = "eatery_blue_backend.urls" - -TEMPLATES = [ - { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "DIRS": [], - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", - ], - }, - }, -] - -WSGI_APPLICATION = "eatery_blue_backend.wsgi.application" - - -# Database -# https://docs.djangoproject.com/en/4.0/ref/settings/#databases - -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", - } -} - - -# Password validation -# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", - }, - { - "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/4.0/topics/i18n/ - -LANGUAGE_CODE = "en-us" - -TIME_ZONE = "UTC" - -USE_I18N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/4.0/howto/static-files/ - -STATIC_URL = "static/" - -# Default primary key field type -# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/eatery_blue_backend/urls.py b/eatery_blue_backend/urls.py deleted file mode 100644 index 8db22b1..0000000 --- a/eatery_blue_backend/urls.py +++ /dev/null @@ -1,22 +0,0 @@ -"""eatery_blue_backend URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/4.0/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" -from django.contrib import admin -from django.urls import include, path - -urlpatterns = [ - path("api", include("api.urls")), - path("admin/", admin.site.urls), -] diff --git a/eatery_blue_backend/wsgi.py b/eatery_blue_backend/wsgi.py deleted file mode 100644 index 3455c86..0000000 --- a/eatery_blue_backend/wsgi.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -WSGI config for eatery_blue_backend project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ -""" - -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") - -application = get_wsgi_application() diff --git a/manage.py b/manage.py deleted file mode 100755 index b469b27..0000000 --- a/manage.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -"""Django's command-line utility for administrative tasks.""" -import os -import sys - - -def main(): - """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") - try: - from django.core.management import execute_from_command_line - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) - - -if __name__ == "__main__": - main() diff --git a/src/api/dfg/system/LeftMerge.py b/src/api/dfg/system/LeftMerge.py index 557daf1..7d47366 100644 --- a/src/api/dfg/system/LeftMerge.py +++ b/src/api/dfg/system/LeftMerge.py @@ -1,5 +1,5 @@ from api.dfg.DfgNode import DfgNode -from typing import Callable, TypeVar +from typing import Callable, TypeVar, Any from functools import cmp_to_key T = TypeVar("T") @@ -15,7 +15,7 @@ def __init__(self, left: DfgNode, right: DfgNode, comparator: Callable[[T, T], i def children(self): return [self.left, self.right] - def __call__(self, *args, **kwargs): + def __call__(self: Any, *args, **kwargs): left_lst = sorted(self.left(*args, **kwargs), key=cmp_to_key(self.comparator)) right_lst = sorted(self.right(*args, **kwargs), key=cmp_to_key(self.comparator)) left_json = _pop_first(left_lst) @@ -39,6 +39,10 @@ def __call__(self, *args, **kwargs): else: merged_lst.append(right_json) right_json = _pop_first(right_lst) + if left_json is not None: + merged_lst.append(left_json) + if right_json is not None: + merged_lst.append(right_json) merged_lst.extend(left_lst) merged_lst.extend(right_lst) return merged_lst diff --git a/static_sources/cornell_dining_now_eateries.json b/static_sources/cornell_dining_now_eateries.json deleted file mode 100644 index 2d2ac1b..0000000 --- a/static_sources/cornell_dining_now_eateries.json +++ /dev/null @@ -1 +0,0 @@ -{"status":"success","data":{"eateries":[{"id":31,"slug":"104-West","name":"104West!","nameshort":"104West!","about":"
\r\nLocated next to the Center for Jewish Living building on the south edge of west campus, 104West!<\/strong> is Cornell's kosher and multicultural
dining room<\/a>. Menus are prepared under the supervision of STAR-K (meat and pareve) and STAR-D (dairy) Kosher Certifications, and Jewish dietary laws are strictly followed with the direction of a resident \"Mashgiach,\" or kosher-food supervisor.\r\n

\r\nYou don't have to keep kosher to enjoy the menu\u2013come sample traditional kosher entrees and enjoy mouth-watering ethnic and international options with a kosher flair. Dining options also include Halal, Seventh-day Adventist, vegetarian, vegan, and other diets.\r\n

\r\nShabbat dinner times vary over the course of the year, based on sunset times. Save money and help us plan by making advance reservations for Shabbat dinners and holiday meals at
https:\/\/kosher.scl.cornell.edu<\/a>!\r\n

\r\nMore Info:
104West!<\/strong><\/a>\r\n

\r\n
\"104West!<\/a>\r\n

","aboutshort":"Cornell's kosher and multicultural dining room is STAR-K and STAR-D certified.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"vlpa2hk9677m9bcbh6n2dtpn7k@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-272-6907","contactEmail":null,"serviceUnitId":9,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.444266,"longitude":-76.487598,"location":"104 West Avenue","coordinates":{"latitude":42.444266,"longitude":-76.487598},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":7,"slug":"Amit-Bhatia-Libe-Cafe","name":"Amit Bhatia Libe Caf\u00e9","nameshort":"Amit Bhatia Libe Caf\u00e9","about":"
A combined effort between Cornell Dining and Olin Library, the Amit Bhatia Libe Caf\u00e9<\/strong> serves specialty Starbucks coffees, smoothies, pastries, and Freshtake Grab-n-Go sandwiches, salads, and snacks.\r\n

\r\n\r\nMore Info:
Amit Bhatia Libe Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Amit<\/a>\r\n

","aboutshort":"The perfect place to take a study break, or to enjoy a latte and a pastry while you enjoy wireless Internet access on your laptop.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"g1pfs9edl1ks5o2dbc58e7fhm8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-4344","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448019,"longitude":-76.484499,"location":"Olin Library","coordinates":{"latitude":42.448019,"longitude":-76.484499},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640124000,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640210400,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640296800,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":8,"slug":"Atrium-Cafe","name":"Atrium Caf\u00e9","nameshort":"Atrium Caf\u00e9","about":"
The Atrium Caf\u00e9's<\/strong> coffee kiosk proudly serves Starbucks specialty coffee, pastries, and Grab-n-Go items, with extended hours. When class is in session, the coffee kiosk opens weekdays at 7am, and closes at 4pm Monday through Thursday, and 2pm Friday.\r\n

\r\nMore Info:
Atrium Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Atrium<\/a>\r\n

","aboutshort":"The Atrium Caf\u00e9 is located in historic Sage Hall, home to the Johnson Graduate School of Management. Coffee kiosk is open extended hours!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"9g3c81c0p2loacsbvrjj5o371c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7591","contactEmail":null,"serviceUnitId":9999,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.445784,"longitude":-76.483232,"location":"Sage Hall","coordinates":{"latitude":42.445784,"longitude":-76.483232},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":1,"slug":"Bear-Necessities","name":"Bear Necessities Grill & C-Store","nameshort":"Bear Necessities","about":"
The grill serves up deluxe burgers, hot chicken sandwiches, delicious french fries, and other mouth-watering comfort foods. You can also order 5 Star Subs and homemade pizza. Bear Necessities<\/strong> also has a convenience store with staple food, beverages and household items.\r\n

\r\n\r\nMore Info:
Bear Necessities<\/strong><\/a>\r\n

\r\n
\"Bear<\/a>\r\n

","aboutshort":"Bear Necessities is a convenience store and grill located on the first floor of Robert Purcell Community Center on north campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h319a8fk4b5lv0644ebkskhha8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-8227","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455777,"longitude":-76.477657,"location":"RPCC - first floor","coordinates":{"latitude":42.455777,"longitude":-76.477657},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640134800,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640221200,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Convenience Store","descrshort":"convenience store"}],"diningCuisines":[{"name":"Pizza","nameshort":"Pizza","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Grill - 5-Star Subs","category":"Grill","item":"5-Star Subs","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Pizza","category":"Pasta & Pizza","item":"Pizza","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Calzones","category":"Pasta & Pizza","item":"Calzones","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Misc - Mozzarella Sticks","category":"Misc","item":"Mozzarella Sticks","healthy":false,"showCategory":false},{"descr":"Misc - Onion Petals","category":"Misc","item":"Onion Petals","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":25,"slug":"Becker-House-Dining","name":"Becker House Dining Room","nameshort":"Becker House Dining","about":"
\r\nThe Becker House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
Becker House Dining Room<\/strong><\/a>\r\n

\r\n
\"Becker<\/a>\r\n

","aboutshort":"Dining room located in Carl Becker House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"di2s9rofto7m8innt5e8vftl0o@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8882","contactEmail":null,"serviceUnitId":2,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.448336,"longitude":-76.489606,"location":"Carl Becker House","coordinates":{"latitude":42.448336,"longitude":-76.489606},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":10,"slug":"Big-Red-Barn","name":"Big Red Barn","nameshort":"Big Red Barn","about":"
\r\nThe Big Red Barn<\/strong> is a cozy caf\u00e9 with a delicious breakfast and lunch menu. It also serves as Cornell's on-campus social center for graduate and professional students. Relax at outdoor picnic tables, or sit inside by the fireplace in comfortable lounge furniture.\r\n

\r\nMore Info:
Big Red Barn<\/strong><\/a>\r\n

\r\n
\"Big<\/a>\r\n

","aboutshort":"Once a carriage house, the Big Red Barn is now a cozy caf\u00e9 with a delicious breakfast and lunch menu.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"u1kmovdep2qlmr86io8h4p3ee8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0428","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448526,"longitude":-76.48098,"location":"Big Red Barn","coordinates":{"latitude":42.448526,"longitude":-76.48098},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":11,"slug":"Bug-Stop-Bagels","name":"Bus Stop Bagels","nameshort":"Bus Stop Bagels","about":"
\r\nBus Stop Bagels<\/strong>, next to the Trillium food court in Kennedy Hall, gets fresh bagels delivered twice a day from Ithaca Bakery with all the usual toppings and a great selection of house sandwiches, plus Starbucks coffee drinks and snack foods.\r\n

\r\nMore Info:
Bus Stop Bagels<\/strong><\/a>\r\n

\r\n
\"Bus<\/a>\r\n

","aboutshort":"Bagels for breakfast, bagels for lunch, bagels to go!","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"rpp0nrlp282t9h18hhol5f0dkc@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447839,"longitude":-76.479456,"location":"Kennedy Hall","coordinates":{"latitude":42.447839,"longitude":-76.479456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640116800,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640203200,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640289600,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Coffee Bar - 96oz Coffee2Go","category":"Coffee Bar","item":"96oz Coffee2Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":12,"slug":"Cafe-Jennie","name":"Caf\u00e9 Jennie","nameshort":"Caf\u00e9 Jennie","about":"
\r\nLocated in The Cornell Store, Caf\u00e9 Jennie is named for Jennie McGraw, the daughter of John McGraw, a wealthy industrialist and a founding Cornell Trustee. Stop by for a Peet's Coffee, delicious Cheesecake Factory baked goods, and a mouth-watering array of sandwiches and wraps.\r\n

\r\nMore Info:
Caf\u00e9 Jennie<\/strong><\/a>\r\n

\r\n
\"Caf\u00e9<\/a>\r\n

","aboutshort":"A caf\u00e9 and sandwich\/pastry shop located in The Cornell Store.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"geron0aq1ooj7jugmcmdc2s2cc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8095","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446851,"longitude":-76.484376,"location":"The Cornell Store","coordinates":{"latitude":42.446851,"longitude":-76.484376},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640098800,"endTimestamp":1640120400,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640185200,"endTimestamp":1640206800,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640271600,"endTimestamp":1640286000,"start":"10:00am","end":"2:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Javiva","category":"Coffee Bar","item":"Javiva","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":2,"slug":"Carols-Cafe","name":"Carol's Caf\u00e9","nameshort":"Carol's Caf\u00e9","about":"
\r\nCome visit Carol's Caf\u00e9<\/strong> and enjoy a menu featuring specialty coffee from Starbucks, smoothies, hot daily soup selections, sushi, fresh fruit and snacks, Freshtake Grab-n-Go sandwiches and salads, delicious pastries and baked goods and more.\r\n

\r\nMore Info:
Carol's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Carol's<\/a>\r\n

","aboutshort":"Warm, inviting caf\u00e9 at Carol Tatkon Center open to the whole campus community.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"05r2nhfjbnknmsccgd6u8dij0g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2257","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453236,"longitude":-76.479404,"location":"Carol Tatkon Center, Balch Hall","coordinates":{"latitude":42.453236,"longitude":-76.479404},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":26,"slug":"Cook-House-Dining","name":"Cook House Dining Room","nameshort":"Cook House Dining","about":"
\r\nThe Cook House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. The eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Cook House Dining Room<\/strong><\/a>\r\n

\r\n
\"Cook<\/a>\r\n

","aboutshort":"Dining room located in Alice Cook House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"27hli58rto1hpf15m3sbe54sak@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-9508","contactEmail":null,"serviceUnitId":1,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.44889,"longitude":-76.488919,"location":"Alice Cook House","coordinates":{"latitude":42.44889,"longitude":-76.488919},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":14,"slug":"Cornell-Dairy-Bar","name":"Cornell Dairy Bar","nameshort":"Cornell Dairy Bar","about":"
\r\nIn the beautifully renovated Stocking Hall on the east end of Tower Road, the Cornell Dairy Bar is a great place for breakfast, coffee, or even sweet treat like Cornell ice cream, sundaes and floats. Regular hours vary seasonally, especially weekend hours, so please check the specific hours on this page for updates.\r\n

\r\nMore Info:
Cornell Dairy Bar<\/strong><\/a>\r\n

\r\n
\"Cornell<\/a>\r\n

","aboutshort":"In the renovated Stocking Hall, the Cornell Dairy features a variety of delicious Cornell Dairy ice cream, sundaes, and floats.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"prvu4v0nr4eu94mqu9q7busa6g@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7660","contactEmail":"dairybar@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447301,"longitude":-76.471398,"location":"Stocking Hall","coordinates":{"latitude":42.447301,"longitude":-76.471398},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"icecream-pink"},{"id":41,"slug":"Crossings-Cafe","name":"Crossings Caf\u00e9","nameshort":"Crossings Caf\u00e9","about":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","aboutshort":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"loadevpceh49fl3c8cuheb2dvk@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.4558,"longitude":-76.479386,"location":"Toni Morrison Hall","coordinates":{"latitude":42.4558,"longitude":-76.479386},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640124000,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640210400,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Mexican - Quesadillas","category":"Mexican","item":"Quesadillas","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Peet's Coffee","category":"Coffee Bar","item":"Peet's Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":32,"slug":"frannys","name":"Franny's","nameshort":"Franny's","about":"One of Cornell's newest eateries is Franny's, a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls. You'll find a unique mix of Pan-Asian sandwiches, rice bowls and ramen, bao buns and fries, as well as refreshing Asian-inspired drinks.","aboutshort":"Franny's is a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls, featuring a unique mix of Asian-inspired cuisine.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekdays","googleCalendarId":"cornell.edu_26ui0ai54lp0cdp2m3j27ci86c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0293","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.451053,"longitude":-76.483884,"location":"Next to Sibley Hall","coordinates":{"latitude":42.451053,"longitude":-76.483884},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[{"name":"Asian","nameshort":"Asian","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Asian - Rice Bowls","category":"Asian","item":"Rice Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Noodle Bowls","category":"Asian","item":"Noodle Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Banh Mi Sandwiches","category":"Asian","item":"Banh Mi Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":16,"slug":"Goldies-Cafe","name":"Goldie's Caf\u00e9","nameshort":"Goldie's Caf\u00e9","about":"
\r\nGoldie's is a great location on Central Campus for breakfast, lunch, or a mid-day snack. Signature sandwiches \u2013 some served on German-style pretzel rolls \u2013 have become customer favorites, and a wide array of Freshtake Grab-n-Go items, snacks, and desserts are always on the menu.\r\n

\r\nMore Info:
Goldie's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Goldie's<\/a>\r\n

","aboutshort":"Conveniently located in the Physical Sciences Building on Central Campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"kb9ce5jj2f6oli3c90tc7j6peo@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-6775","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450064,"longitude":-76.481503,"location":"Physical Sciences Building","coordinates":{"latitude":42.450064,"longitude":-76.481503},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":15,"slug":"Green-Dragon","name":"Green Dragon","nameshort":"Green Dragon","about":"
\r\nStop by the Green Dragon<\/strong> and enjoy a hot specialty Finger Lakes Coffee Roasters coffee, Freshtake Grab-n-Go sandwiches and salads, kosher items, and delicious desserts.\r\n

\r\nMore Info:
Green Dragon<\/strong><\/a>\r\n

\r\n
\"Green<\/a>\r\n

","aboutshort":"A hot spot on central campus, especially for the students, faculty, and staff of College of Architecture, Art and Planning.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"7sii70faon9ta2vpoehr69415s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3327","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450948,"longitude":-76.484456,"location":"Sibley Hall","coordinates":{"latitude":42.450948,"longitude":-76.484456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":24,"slug":"Hot-Dog-Cart","name":"Hot Dog Cart","nameshort":"Hot Dog Cart","about":"
\r\nCornell Dining's Hot Dog Cart<\/strong> is usually open outside Day Hall on summer weekdays when weather permits. On special occasions, the Hot Dog Cart may be elsewhere on campus. Keep an eye on Cornell Dining's
Facebook<\/a> and Twitter<\/a> for updates.\r\n

\r\nMore Info:
Hot Dog Cart<\/strong><\/a>\r\n

\r\n
\"Hot<\/a>\r\n

","aboutshort":"Enjoy lunch al fresco when weather permits, choosing an all-beef hot dog or vegetarian (tofu) hot dog.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"11:00am - 2:00pm weekdays, weather permitting","googleCalendarId":"cornell.edu_eaq3euadrebh0dmgqt618l7tgs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":34,"slug":"icecreamcart","name":"Ice Cream Bike","nameshort":"Ice Cream Bike","about":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","aboutshort":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekday middays for the summer, and Friday evenings for Cornell's concert series.","googleCalendarId":"cornell.edu_3kuppj4nsjes2b42jhpno5u97g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"icecream-pink"},{"id":27,"slug":"Jansens-Dining","name":"Jansen's Dining Room at Bethe House","nameshort":"Jansen's Dining","about":"
\r\nJansen's Dining Room at Bethe House<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Chef Jacob Kuehn puts together exciting new menus throughout the year. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Jansen's Dining Room at Bethe House<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Dining room located in Hans Bethe House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h0nfohf0d90ot1rmukjphj7ajc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1736","contactEmail":null,"serviceUnitId":5,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447116,"longitude":-76.48864,"location":"Hans Bethe House","coordinates":{"latitude":42.447116,"longitude":-76.48864},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":28,"slug":"Jansens-Market","name":"Jansen's Market","nameshort":"Jansen's Market","about":"
\r\nIn addition to an array of snacks, beverages, fresh and frozen take-away meals, household items, and pharmacy and beauty supplies, Jansen's Market<\/strong> also serve Starbucks coffee, bubble tea, smoothies, pastries, frozen yogurt, and Dreamfactory cheesecake.\r\n

\r\nMore Info:
Jansen's Market<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Full-service convenience store located on the first floor of Noyes Community Recreation Center on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"0dqnc6l2mt25okch8nimsnojhg@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-4997","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446325,"longitude":-76.487932,"location":"Noyes Community Recreation Center","coordinates":{"latitude":42.446325,"longitude":-76.487932},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Convenience Store","descrshort":"convenience store"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Bubble Tea","category":"Coffee Bar","item":"Bubble Tea","healthy":false,"showCategory":false},{"descr":"Misc - Peanut Butter Sandwich Bar","category":"Misc","item":"Peanut Butter Sandwich Bar","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":29,"slug":"Keeton-House-Dining","name":"Keeton House Dining Room","nameshort":"Keeton House Dining","about":"
\r\nThe Keeton House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Sample Chef Nery's unique menu offerings in the entr\u00e9e station, Asian station, grill, deli, and salad bar. This eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Keeton House Dining Room<\/strong><\/a>\r\n

\r\n
\"Keeton<\/a>\r\n

","aboutshort":"Dining room located in William Keeton House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ekd72jfc2qai617oloa2b0ibp0@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3033","contactEmail":null,"serviceUnitId":3,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446942,"longitude":-76.489992,"location":"William Keeton House","coordinates":{"latitude":42.446942,"longitude":-76.489992},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":42,"slug":"Mann-Cafe","name":"Mann Caf\u00e9","nameshort":"Mann Caf\u00e9","about":"Take a study break at Mann Caf\u00e9!","aboutshort":"Take a study break at Mann Caf\u00e9!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ppbukfcjo05629gk0t4fu26aro@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448799,"longitude":-76.47851,"location":"Mann Library on the Ag Quad","coordinates":{"latitude":42.448799,"longitude":-76.47851},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Mexican - Burritos","category":"Mexican","item":"Burritos","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":18,"slug":"Marthas-Cafe","name":"Martha's Caf\u00e9","nameshort":"Martha's Cafe","about":"
\r\nMartha's Caf\u00e9<\/strong>, in Martha Van Rensselaer Hall, offers made-to-order Mediterranean-inspired grain and salad bowls and wraps, composed bowls, Copper Horse Coffee, and breakfast!\r\n

\r\nMore Info:
Martha's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Martha's<\/a>\r\n

","aboutshort":"Fresh food with a Mediterranean flair.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sperf092mrbt796rr36toeqrus@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-8080","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450115,"longitude":-76.479237,"location":"Martha Van Rensselaer Hall","coordinates":{"latitude":42.450115,"longitude":-76.479237},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Milk","category":"Cornell Dairy","item":"Milk","healthy":false,"showCategory":true},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false},{"descr":"Lunch - Hot and Cold Mediterranean Bowls","category":"Lunch","item":"Hot and Cold Mediterranean Bowls","healthy":true,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":19,"slug":"Mattins-Cafe","name":"Mattin's Caf\u00e9","nameshort":"Mattin's Caf\u00e9","about":"
\r\nYou\u2019ll find Mattin's Caf\u00e9<\/strong> in the atrium of Duffield Hall, adjacent to Phillips Hall on the Engineering Quad. Enjoy made-to-order deli sandwiches, boneless wings, hot Starbucks coffee, Freshtake Grab-n-Go items, soups, kosher items, mouth-watering pastries and more.\r\n

\r\nMore Info:
Mattin's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Mattin's<\/a>\r\n

","aboutshort":"Popular with engineers and a go to spot in the stunning atrium of Duffield Hall.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"1qman2n728pqjuq5ntaoofc7v0@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-4581","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.444162,"longitude":-76.482287,"location":"Duffield Hall","coordinates":{"latitude":42.444162,"longitude":-76.482287},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640113200,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640199600,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":33,"slug":"mccormicks","name":"McCormick's at Moakley House","nameshort":"McCormick's","about":"McCormick's at Moakley House offers a casual environment close to campus, open daily through the season. Treat a campus visitor to an impeccably served luncheon, stop for refreshments after a round of golf, meet coworkers at the end of the day for a burger and a beer, or bring the family for a relaxing weekend brunch.\r\n\r\nNamed for Jack McCormick, a varsity golfer from the Cornell Class of 1957 whose will included a bequest to modernize Moakley House, this eatery alongside the award-winning Robert Trent Jones Golf Course at Cornell University isn't just for golfers. It's open to the public and has plenty of parking, and we welcome everyone for a drink, a snack, or a meal.\r\n\r\nMcCormick's is closed for the 2021 season as of September 24th.\r\n\r\n

\r\nMore Info:
McCormick's at Moakley House<\/strong><\/a>\r\n

","aboutshort":"Named for Jack McCormick, a varsity golfer from the Cornell Class of 1957, McCormick's is open to the public and has plenty of parking.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"cornell.edu_gqlmrg7n0qk8qihl75ru2u1p80@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-6536","contactEmail":"mccormicks@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.458324,"longitude":-76.469539,"location":"Robert Trent Jones Golf Course","coordinates":{"latitude":42.458324,"longitude":-76.469539},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false}],"announcements":[{"id":199,"title":"McCormick's is closed for the 2021 season after Friday, September 24th.","announceType":"EATERY","startTimestamp":1632283200,"stopTimestamp":1640408340}],"icon":"coffee-brown"},{"id":3,"slug":"North-Star","name":"North Star Dining Room","nameshort":"North Star","about":"
\r\nThe North Star Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Enjoy an open kitchen concept and numerous unique food stations serving a huge variety of ethnic and regional American cuisine. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
North Star Dining Room<\/strong><\/a>\r\n

\r\n
\"North<\/a>\r\n

","aboutshort":"Dining room located in Appel Commons on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ecbhqf3ibeei09dds91viod5g8@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2992","contactEmail":null,"serviceUnitId":7,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453527,"longitude":-76.475944,"location":"Appel Commons, Third floor","coordinates":{"latitude":42.453527,"longitude":-76.475944},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[{"id":216,"title":"North Star Dining Room is closed for renovation through the Spring 2022 semester!","announceType":"EATERY","startTimestamp":1639976400,"stopTimestamp":1654055940}],"icon":"restaurant-red"},{"id":20,"slug":"Okenshields","name":"Okenshields","nameshort":"Okenshields","about":"
\r\nOkenshields<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. With hundreds of menu options to choose from \u2013 from an Asian station to a healthy foods bar featuring whole grain salads and cut fruit \u2013 you're sure to find something that meets your fancy.\r\n

\r\nMore Info:
Okenshields<\/strong><\/a>\r\n

\r\n
\"Okenshields<\/a>\r\n

","aboutshort":"Dining room located in Willard Straight Hall on Central Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"3hku0mr66kapq1lh8fakug9kko@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6636","contactEmail":null,"serviceUnitId":10,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446491,"longitude":-76.485678,"location":"Willard Straight Hall","coordinates":{"latitude":42.446491,"longitude":-76.485678},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":4,"slug":"Risley-Dining","name":"Risley Dining Room","nameshort":"Risley Dining Room","about":"
\r\nRisley Dining Room<\/strong> is a 100% gluten-free, tree-nut-free, and peanut-free
dining room<\/a> for house residents, and for the entire Cornell community. There are always vegan and vegetarian choices. Modeled after the Christchurch Refectory at Oxford University, the dining room maintains the same Gothic charm as when it first opened as the all-Ivy \"Risley Great Hall\" in 1913. \r\n

\r\nMore Info:
Risley Dining Room<\/strong><\/a>\r\n

\r\n
\"Risley<\/a>\r\n

","aboutshort":"Dining room located in Risley Residential College on North Campus.","nutrition":"Risley Dining Room is certified 100% gluten-free, tree-nut-free, and peanut-free. Thank you for not bringing outside food into Risley!<\/strong>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"hq98btd396f3077p88d30c84fs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-4229","contactEmail":null,"serviceUnitId":8,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453117,"longitude":-76.481946,"location":"Risley Residential College","coordinates":{"latitude":42.453117,"longitude":-76.481946},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":5,"slug":"RPCC-Marketplace","name":"Robert Purcell Marketplace Eatery","nameshort":"RPCC Marketplace","about":"
\r\nThe Robert Purcell Marketplace Eatery<\/strong> is an award-winning
dining room<\/a> with a huge variety of menu options. Chef Kevin Moore, who has been with Cornell Dining for over 25 years, most recently at 104West!<\/a>, plans creative menus with roots in a variety of international traditions. Note: Our Late Dinner hours feature a limited menu.\r\n

\r\nMore Info:
Robert Purcell Marketplace Eatery<\/strong><\/a>\r\n

\r\n
\"Robert<\/a>\r\n

","aboutshort":"Dining room located in Robert Purcell Community Center on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"32uglqeiqfo9edhpp4tka8oqsc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1138","contactEmail":null,"serviceUnitId":6,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455973,"longitude":-76.477354,"location":"RPCC, Third floor","coordinates":{"latitude":42.455973,"longitude":-76.477354},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640088000,"endTimestamp":1640100600,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":2,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Pork Breakfast Sausage","healthy":false,"sortIdx":3},{"item":"Home Fries","healthy":true,"sortIdx":4},{"item":"Pancakes with Syrup","healthy":false,"sortIdx":5},{"item":"French Toast Sticks","healthy":false,"sortIdx":6},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":7}]},{"category":"Grill Station","sortIdx":3,"items":[{"item":"Scrambled Tofu","healthy":true,"sortIdx":1},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":2}]},{"category":"Specialty Station","sortIdx":4,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":2},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":3},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":4},{"item":"Waffle Bar","healthy":false,"sortIdx":5},{"item":"Assorted Cereal","healthy":false,"sortIdx":6},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":7}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640100600,"endTimestamp":1640115000,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":5,"items":[{"item":"Beef Vegetable Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":6,"items":[{"item":"Salad Bar","healthy":true,"sortIdx":1},{"item":"Grains For Brains","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":7,"items":[{"item":"Pasta Primavera","healthy":false,"sortIdx":1},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":8,"items":[{"item":"Potato Tots","healthy":false,"sortIdx":1},{"item":"Calabacitas","healthy":true,"sortIdx":2},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":9,"items":[{"item":"Hacienda Cheese Quesadilla","healthy":false,"sortIdx":1},{"item":"Grilled Cheese Sandwich","healthy":false,"sortIdx":2}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640124000,"endTimestamp":1640133000,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":10,"items":[{"item":"Cornell Chicken Noodle Soup","healthy":false,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":11,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2},{"item":"Grains For Brains","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":12,"items":[{"item":"Salmon","healthy":false,"sortIdx":1},{"item":"Spanish Style Brown Rice & Beans","healthy":true,"sortIdx":2},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Sides","sortIdx":13,"items":[{"item":"Steamed Vegetable Melange","healthy":true,"sortIdx":1},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":2},{"item":"Sauteed Zucchini","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4},{"item":"Fried Potato Puffs","healthy":false,"sortIdx":5}]},{"category":"Grill Station","sortIdx":14,"items":[{"item":"BBQ Chicken Pizza","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640174400,"endTimestamp":1640187000,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":15,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Turkey Breakfast Sausage","healthy":true,"sortIdx":3},{"item":"Bacon","healthy":false,"sortIdx":4},{"item":"Steamed Brown Rice","healthy":true,"sortIdx":5},{"item":"Home Fries","healthy":true,"sortIdx":6}]},{"category":"Grill Station","sortIdx":16,"items":[{"item":"Bacon & Cheese Omelet","healthy":false,"sortIdx":1},{"item":"Steamed Fresh Vegetables","healthy":true,"sortIdx":2},{"item":"Western Scrambled Tofu","healthy":true,"sortIdx":3},{"item":"Build Your Own Omelette","healthy":true,"sortIdx":4},{"item":"Cheese Omelet","healthy":false,"sortIdx":5}]},{"category":"Specialty Station","sortIdx":17,"items":[{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":1},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":2},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":3},{"item":"Waffle Bar","healthy":false,"sortIdx":4},{"item":"Assorted Cereal","healthy":false,"sortIdx":5},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":6}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640187000,"endTimestamp":1640201400,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":18,"items":[{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":19,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Salad Bar","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":20,"items":[{"item":"Pasta with Sun-dried Tomato Basil & Feta","healthy":true,"sortIdx":1},{"item":"Kale Pesto Chicken","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":21,"items":[{"item":"Chef's Choice Seasonal Vegetable","healthy":true,"sortIdx":1},{"item":"Seared Kale","healthy":true,"sortIdx":2},{"item":"Rosemary Roasted Red Skin Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":22,"items":[{"item":"Grilled Chicken Breast","healthy":true,"sortIdx":1},{"item":"Grilled Hot Dogs","healthy":false,"sortIdx":2},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4}]},{"category":"Pizza Station","sortIdx":23,"items":[{"item":"Supreme Vegetable Pizza","healthy":false,"sortIdx":1},{"item":"White Garlic Hawaiian Pizza","healthy":false,"sortIdx":2}]},{"category":"Wok\/Asian Station","sortIdx":24,"items":[{"item":"Moo Goo Gai Pan","healthy":false,"sortIdx":1},{"item":"Vegetable Fried Rice","healthy":false,"sortIdx":2},{"item":"Long Grain Brown Rice","healthy":true,"sortIdx":3},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":4}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640210400,"endTimestamp":1640219400,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":25,"items":[{"item":"Cornell Cream of Mushroom Soup","healthy":false,"sortIdx":1},{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":2}]},{"category":"Salad Bar Station","sortIdx":26,"items":[{"item":"Grains For Brains","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Entrees","sortIdx":27,"items":[{"item":"Roasted Eggplant & Zucchini Casserole","healthy":false,"sortIdx":1},{"item":"Chef's Choice Vegan Entree","healthy":false,"sortIdx":2},{"item":"Chef's Choice Meat Entree","healthy":false,"sortIdx":3},{"item":"Roasted Pork Loin with Mango Mojo","healthy":true,"sortIdx":4},{"item":"Gourmet Pretzel Bar","healthy":false,"sortIdx":5}]},{"category":"Hot Traditional Station - Sides","sortIdx":28,"items":[{"item":"Steamed Winter Vegetables","healthy":true,"sortIdx":1},{"item":"Sauteed Super Greens","healthy":true,"sortIdx":2},{"item":"Roasted Garlic & Herb Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":29,"items":[{"item":"Veggie Burger","healthy":true,"sortIdx":1},{"item":"Char-Grilled Hamburgers","healthy":false,"sortIdx":2},{"item":"Build Your Own Nachos","healthy":false,"sortIdx":3}]},{"category":"Pasta and Pizza Station","sortIdx":30,"items":[{"item":"Pasta By Design","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2},{"item":"Broccoli Alfredo Pizza","healthy":false,"sortIdx":3},{"item":"Buffalo Chicken Pizza","healthy":false,"sortIdx":4}]},{"category":"Wok\/Asian Station","sortIdx":31,"items":[{"item":"Pancake Bar with Fruit Toppings & Syrups","healthy":false,"sortIdx":1},{"item":"Omelet Bar","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640260800,"endTimestamp":1640273400,"start":"7:00am","end":"10:30am","menu":[],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640273400,"endTimestamp":1640287800,"start":"10:30am","end":"2:30pm","menu":[],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640296800,"endTimestamp":1640305800,"start":"5:00pm","end":"7:30pm","menu":[],"calSummary":"Dinner"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":30,"slug":"Rose-House-Dining","name":"Rose House Dining Room","nameshort":"Rose House Dining","about":"
\r\nThe Rose House Dining Room<\/strong> is a
dning room<\/a> for house residents, and for the entire Cornell community. This eatery features traditional hot entrees, an Asian station, a grill, a deli, and a salad bar. Continental breakfast on Saturday and Sunday. Sample Chef Matt Seeber's daily menu options \u2013 you won't be disappointed!\r\n

\r\nMore Info:
Rose House Dining Room<\/strong><\/a>\r\n

\r\n
\"Rose<\/a>\r\n

","aboutshort":"Dining room located in Flora Rose House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"mo4mqfpe88ucqaer728ovfei18@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-0337","contactEmail":null,"serviceUnitId":4,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447813,"longitude":-76.488791,"location":"Flora Rose House","coordinates":{"latitude":42.447813,"longitude":-76.488791},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":21,"slug":"Rustys","name":"Rusty's","nameshort":"Rusty's","about":"
\r\nConveniently located in the lobby of Uris Hall, Rusty's<\/strong> offers Starbucks specialty coffees, Straight from the Oven baked goods, Freshtake Grab-n-Go sandwiches, salads and more. At any time of day, you're sure to find something to whet your appetite!\r\n

\r\nMore Info:
Rusty's<\/strong><\/a>\r\n

\r\n
\"Rusty's<\/a>\r\n

","aboutshort":"A great place to grab a quick coffee or a bite to eat on your way to class or work.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sqp9nd9rt727fm7v2sgmfelkps@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6656","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447399,"longitude":-76.482302,"location":"Uris Hall","coordinates":{"latitude":42.447399,"longitude":-76.482302},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":13,"slug":"StraightMarket","name":"Straight from the Market","nameshort":"Straight from the Market","about":"With a farm-fresh marketplace flair, Straight from the Market offers a wide variety of fresh and marinated vegetables, hummus and tapenade bar, salad fixings, and Cornell Dairy ice cream on the main floor of Willard Straight Hall adjacent to the Straight Terrace. Healthy, flavorful, and ready-to-go meat, vegan, and vegetarian choices daily.","aboutshort":"A farm-fresh marketplace on the main floor of the Straight.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ju94n6trv0ccoqcnd5u7otle50@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3963","contactEmail":null,"serviceUnitId":11,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446372,"longitude":-76.485786,"location":"Willard Straight Hall","coordinates":{"latitude":42.446372,"longitude":-76.485786},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":23,"slug":"Trillium","name":"Trillium","nameshort":"Trillium","about":"
\r\nAt Trillium<\/strong> choose from a wide variety of food stations serving Mexican food, Asian dishes, sumptuous burgers, pasta bar, sandwiches, salads, soups, and made-to-order omelets, among many other delectable menu options.\r\n

\r\nMore Info:
Trillium<\/strong><\/a>\r\n

\r\n
\"Trillium<\/a>\r\n

","aboutshort":"Located in Kennedy Hall in the heart of Central Campus, Trillium is one of our most popular food courts.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"i8v43jd76mugc62voucp4dqn9s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447883,"longitude":-76.479127,"location":"Kennedy Hall","coordinates":{"latitude":42.447883,"longitude":-76.479127},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Food Court","descrshort":"food court"}],"diningCuisines":[{"name":"Mexican","nameshort":"Mexican","descr":""},{"name":"Asian","nameshort":"Asian","descr":""},{"name":"Pizza","nameshort":"Pizza","descr":""},{"name":"Italian","nameshort":"Italian","descr":null}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"fastfood-blue"}]},"message":null,"meta":{"copyright":"Cornell University, Cornell Dining","responseDttm":"2021-12-22T11:15:18-0500"}} \ No newline at end of file diff --git a/static_sources/external_eateries.json b/static_sources/external_eateries.json deleted file mode 100644 index a2cf74f..0000000 --- a/static_sources/external_eateries.json +++ /dev/null @@ -1,600 +0,0 @@ -{ - "eateries": [ - { - "id": 33, - "slug": "Terrace", - "external": true, - "name": "Terrace Restaurant", - "nameshort": "Terrace", - "about": "", - "contactPhone": "1-800-541-2501", - "coordinates": { - "latitude": 42.446267, - "longitude": -76.482314 - }, - "location": "Statler", - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "10:00am", - "end": "3:00pm", - "menu": [] - } - ] - } - ], - "datesClosed": [ - "9/6/21", - "11/25/21", - "11/26/21", - "11/27/21", - "11/28/21", - "12/20/21-1/20/22" - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - }, - { - "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", - "descrshort": "Meal Plan - Debit" - } - ], - "diningItems": [ - { - "item": "Salads", - "healthy": true, - "category": "General" - }, - { - "item": "Burrito Bowl", - "healthy": false, - "category": "General" - }, - { - "item": "Burrito", - "healthy": true, - "category": "General" - }, - { - "item": "Chicken Tenders", - "healthy": false, - "category": "General" - }, - { - "item": "Fries", - "healthy": false, - "category": "General" - }, - { - "item": "Pho", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 34, - "slug": "Macs", - "external": true, - "name": "Mac's Café", - "nameshort": "Mac's", - "about": "", - "cornellDining": false, - "contactPhone": "1-800-541-2501", - "coordinates": { - "latitude": 42.445921, - "longitude": -76.481984 - }, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Statler Hotel", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "9:30am", - "end": "5:30pm", - "menu": [] - } - ] - } - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - }, - { - "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", - "descrshort": "Meal Plan - Debit" - } - ], - "datesClosed": [ - "8/18/21", - "8/19/21", - "8/20/21", - "8/21/21", - "8/22/21", - "8/23/21", - "9/6/21", - "10/9/21", - "10/10/21", - "10/11/21", - "10/12/21", - "11/24/21", - "11/25/21", - "11/26/21", - "11/27/21", - "11/28/21", - "12/16/21", - "12/17/21", - "12/20/21-1/20/22" - ], - "diningItems": [ - { - "item": "Pizza", - "healthy": false, - "category": "General" - }, - { - "item": "Pasta", - "healthy": false, - "category": "General" - }, - { - "item": "Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Sushi to Go", - "healthy": false, - "category": "General" - }, - { - "item": "Soft Drinks", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 35, - "slug": "Zeus", - "external": true, - "name": "Temple of Zeus", - "nameshort": "Temple of Zeus", - "about": "", - "cornellDining": false, - "contactPhone": "", - "coordinates": { - "latitude": 42.449091, - "longitude": -76.483414 - }, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Goldwin Smith Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "8:00am", - "end": "5:00pm", - "menu": [] - } - ] - } - ], - "datesClosed": [ - "09/06/21" - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Sandwiches", - "healthy": true, - "category": "General" - }, - { - "item": "Soups", - "healthy": true, - "category": "General" - }, - { - "item": "Baked Goods", - "healthy": true, - "category": "General" - }, - { - "item": "Candy", - "healthy": false, - "category": "General" - }, - { - "item": "Soft Drinks", - "healthy": true, - "category": "General" - } - ] - }, - { - "id": 36, - "slug": "Gimme-Coffee", - "external": true, - "name": "Gimme Coffee", - "nameshort": "Gimme Coffee", - "about": "", - "cornellDining": false, - "contactPhone": "1-607-227-5391", - "coordinates": { - "latitude": 42.444958, - "longitude": -76.481169 - }, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Gates Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "8:00am", - "end": "3:00pm", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Coffee", - "healthy": true, - "category": "General" - }, - { - "item": "Baked Goods", - "healthy": true, - "category": "General" - } - ] - }, - { - "id": 37, - "slug": "Louies-Lunch", - "external": true, - "name": "Louie's Lunch", - "nameshort": "Louie's", - "about": "", - "cornellDining": false, - "contactPhone": "1-607-257-4649", - "coordinates": { - "latitude": 42.45336, - "longitude": -76.481225 - }, - "campusArea": { - "descr": "North Campus", - "descrshort": "North" - }, - "location": "Across from Risley", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "11:00am", - "end": "3:00am", - "menu": [] - } - ] - }, - { - "weekday": "saturday", - "events": [ - { - "descr": "General", - "start": "12:00pm", - "end": "3:00am", - "menu": [] - } - ] - }, - { - "weekday": "sunday", - "events": [ - { - "descr": "General", - "start": "6:00pm", - "end": "12:00am", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "French Fries", - "healthy": false, - "category": "General" - }, - { - "item": "Garlic Bread", - "healthy": false, - "category": "General" - }, - { - "item": "Cold Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Subs", - "healthy": false, - "category": "General" - }, - { - "item": "Pizza Subs", - "healthy": false, - "category": "General" - }, - { - "item": "Burgers", - "healthy": false, - "category": "General" - }, - { - "item": "Egg Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Dogs", - "healthy": false, - "category": "General" - }, - { - "item": "Wraps", - "healthy": false, - "category": "General" - }, - { - "item": "Salads", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 38, - "slug": "Anabels-Grocery", - "external": true, - "name": "Anabel's Grocery", - "nameshort": "Anabel's", - "about": "", - "cornellDining": false, - "contactPhone": "", - "coordinates": { - "latitude": 42.445061, - "longitude": -76.485826 - }, - "campusArea": { - "descr": "South Campus", - "descrshort": "South" - }, - "location": "Anabel Taylor Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "wednesday-thursday", - "events": [ - { - "descr": "General", - "start": "3:00pm", - "end": "7:00pm", - "menu": [] - } - ] - }, - { - "weekday": "friday", - "events": [ - { - "descr": "General", - "start": "12:00pm", - "end": "3:00pm", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Cornell Card", - "descrshort": "Cornell Card" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Whole Grains", - "healthy": true, - "category": "General" - }, - { - "item": "Spices", - "healthy": false, - "category": "General" - }, - { - "item": "Legumes", - "healthy": true, - "category": "General" - }, - { - "item": "Fresh and Frozen Produce", - "healthy": true, - "category": "General" - }, - { - "item": "Nuts", - "healthy": true, - "category": "General" - }, - { - "item": "Dried Fruits", - "healthy": true, - "category": "General" - }, - { - "item": "Tofu", - "healthy": true, - "category": "General" - }, - { - "item": "Eggs", - "healthy": false, - "category": "General" - }, - { - "item": "Milks", - "healthy": true, - "category": "General" - }, - { - "item": "Kombucha on Tap", - "healthy": true, - "category": "General" - }, - { - "item": "Bottled Drinks", - "healthy": false, - "category": "General" - }, - { - "item": "Bulk Items", - "healthy": false, - "category": "General" - } - ] - } - ] -} diff --git a/transactions/__init__.py b/transactions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/transactions/admin.py b/transactions/admin.py deleted file mode 100644 index 784107d..0000000 --- a/transactions/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin - -from .models import TransactionHistory -# Register your models here. - -admin.site.register(TransactionHistory) \ No newline at end of file diff --git a/transactions/apps.py b/transactions/apps.py deleted file mode 100644 index f806531..0000000 --- a/transactions/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class TransactionsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'transactions' diff --git a/transactions/controllers/update_transactions_controller.py b/transactions/controllers/update_transactions_controller.py deleted file mode 100644 index 3c1245e..0000000 --- a/transactions/controllers/update_transactions_controller.py +++ /dev/null @@ -1,35 +0,0 @@ -from datetime import datetime, timedelta -from transactions.models import TransactionHistory -from util.constants import vendor_name_to_internal_id - -import pytz -class UpdateTransactionsController: - - def __init__(self, data): - self._data = data - - def process(self): - if self._data["TIMESTAMP"] == "Invalid date": - return 0 - tz = pytz.timezone('America/New_York') - recent_datetime = tz.localize(datetime.strptime(self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p')) - canonical_date = recent_datetime.date() - block_end_time = recent_datetime.time() - if recent_datetime.hour < 4: - # between 12am and 4am associate this with the previous day - canonical_date = canonical_date - timedelta(days=1) - num_inserted = 0 - ignored_names = set() - for place in self._data["UNITS"]: - internal_id = vendor_name_to_internal_id(place["UNIT_NAME"]).value - if internal_id == None: - ignored_names.add(place["UNIT_NAME"]) - else: - num_inserted += 1 - try: - TransactionHistory.objects.create(eatery_id = internal_id, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) - except Exception as e: - print(e) - num_inserted -= 1 - return num_inserted - diff --git a/transactions/management/commands/fetch_recent_transactions.py b/transactions/management/commands/fetch_recent_transactions.py deleted file mode 100644 index b264bf9..0000000 --- a/transactions/management/commands/fetch_recent_transactions.py +++ /dev/null @@ -1,26 +0,0 @@ -# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db - -import requests -import os -from requests.structures import CaseInsensitiveDict -from django.core.management.base import BaseCommand -from transactions.controllers.update_transactions_controller import UpdateTransactionsController - -class Command(BaseCommand): - help = 'Fetches transaction data from a vendor API and adds it to our transaction history database' - - def handle(self, *args, **options): - endpoint = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" - headers = CaseInsensitiveDict() - token = os.environ.get("CORNELL_VENDOR_TOKEN") - api_key = os.environ.get("CORNELL_VENDOR_API_KEY") - headers["Accept"] = "application/json" - headers["Authorization"] = "Bearer {}".format(token) - headers["X-Api-Key"] = api_key - resp = requests.get(endpoint, headers=headers) - num_inserted = 0 - if resp.status_code == 200: - res = UpdateTransactionsController(resp.json()).process() - if res["success"]: - num_inserted = res["result"]["num_inserted"] - # print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file diff --git a/transactions/management/commands/ingest_log_transactions.py b/transactions/management/commands/ingest_log_transactions.py deleted file mode 100644 index 82e713e..0000000 --- a/transactions/management/commands/ingest_log_transactions.py +++ /dev/null @@ -1,30 +0,0 @@ -# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db - -import json -from datetime import datetime -from django.core.management.base import BaseCommand - -from transactions.controllers.update_transactions_controller import UpdateTransactionsController -from transactions.models import TransactionHistory -class Command(BaseCommand): - help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistory table' - - def handle(self, *args, **options): - num_deleted = TransactionHistory.objects.all().delete()[0] - counter = 0 - num_inserted = 0 - with open("static_sources/data.log", "r") as log: - for line in log: - try: - data = json.loads(line) - timestamp = datetime.strptime(data['TIMESTAMP'], '%Y-%m-%d %I:%M:%S %p') - if counter % 100 == 1: - print(timestamp) - if timestamp.year == 2021 and timestamp.month > 7: - counter += 1 - inserted = UpdateTransactionsController(data).process() - num_inserted += inserted - except Exception as e: - pass - print("{} Entries Deleted".format(num_deleted)) - print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file diff --git a/transactions/migrations/0001_initial.py b/transactions/migrations/0001_initial.py deleted file mode 100644 index 5178f47..0000000 --- a/transactions/migrations/0001_initial.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 17:56 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('eateries', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='TransactionHistory', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('canonical_date', models.DateField()), - ('block_end_time', models.TimeField()), - ('transaction_count', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - ), - migrations.AddIndex( - model_name='transactionhistory', - index=models.Index(fields=['canonical_date'], name='transaction_canonic_a40422_idx'), - ), - migrations.AlterUniqueTogether( - name='transactionhistory', - unique_together={('eatery_id', 'block_end_time', 'canonical_date')}, - ), - ] diff --git a/transactions/migrations/__init__.py b/transactions/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/transactions/models.py b/transactions/models.py deleted file mode 100644 index 53d660b..0000000 --- a/transactions/models.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.db import models -from eateries.models import EateryStore -# Create your models here. - -# [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] -class TransactionHistory(models.Model): - class Meta: - unique_together = ('eatery_id', 'block_end_time', 'canonical_date') - indexes = [models.Index(fields = ['canonical_date'])] - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - canonical_date = models.DateField() - block_end_time = models.TimeField() - transaction_count = models.IntegerField() \ No newline at end of file diff --git a/transactions/tests.py b/transactions/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/transactions/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/transactions/views.py b/transactions/views.py deleted file mode 100644 index b91e46a..0000000 --- a/transactions/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. \ No newline at end of file diff --git a/util/constants.py b/util/constants.py deleted file mode 100644 index b9108be..0000000 --- a/util/constants.py +++ /dev/null @@ -1,158 +0,0 @@ -from enum import Enum - -from api.datatype.Eatery import EateryID - -CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" - -class SnapshotFileName(Enum): - EATERY_STORE = "eatery_store.txt" - ALERT_STORE = "alert_store.txt" - CATEGORY_STORE = "category_store.txt" - MENU_STORE = "menu_store.txt" - ITEM_STORE = "item_store.txt" - SUBITEM_STORE = "subitem_store.txt" - CATEGORY_ITEM_ASSOCIATION = "category_item_association.txt" - EVENT_SCHEDULE = "event_schedule.txt" - DAY_OF_WEEK_EVENT_SCHEDULE = "day_of_week_event_schedule.txt" - DATE_EVENT_SCHEDULE = "date_event_schedule.txt" - CLOSED_EVENT_SCHEDULE = "closed_event_schedule.txt" - TRANSACTION_HISTORY = "transaction_history.txt" - -def dining_id_to_internal_id(id: int): - if id == 31: - return EateryID.ONE_ZERO_FOUR_WEST - elif id == 7: - return EateryID.LIBE_CAFE - elif id == 8: - return EateryID.ATRIUM_CAFE - elif id == 1: - return EateryID.BEAR_NECESSITIES - elif id == 25: - return EateryID.BECKER_HOUSE - elif id == 10: - return EateryID.BIG_RED_BARN - elif id == 11: - return EateryID.BUS_STOP_BAGELS - elif id == 12: - return EateryID.CAFE_JENNIE - elif id == 2: - return EateryID.CAROLS_CAFE - elif id == 26: - return EateryID.COOK_HOUSE - elif id == 14: - return EateryID.DAIRY_BAR - elif id == 41: - return EateryID.CROSSINGS_CAFE - elif id == 32: - return EateryID.FRANNYS - elif id == 16: - return EateryID.GOLDIES_CAFE - elif id == 15: - return EateryID.GREEN_DRAGON - elif id == 24: - return EateryID.HOT_DOG_CART - elif id == 34: - return EateryID.ICE_CREAM_BIKE - elif id == 27: - return EateryID.BETHE_HOUSE - elif id == 28: - return EateryID.JANSENS_MARKET - elif id == 29: - return EateryID.KEETON_HOUSE - elif id == 42: - return EateryID.MANN_CAFE - elif id == 18: - return EateryID.MARTHAS_CAFE - elif id == 19: - return EateryID.MATTINS_CAFE - elif id == 33: - return EateryID.MCCORMICKS - elif id == 3: - return EateryID.NORTH_STAR_DINING - elif id == 20: - return EateryID.OKENSHIELDS - elif id == 4: - return EateryID.RISLEY - elif id == 5: - return EateryID.RPCC - elif id == 30: - return EateryID.ROSE_HOUSE - elif id == 21: - return EateryID.RUSTYS - elif id == 13: - return EateryID.STRAIGHT_FROM_THE_MARKET - elif id == 23: - return EateryID.TRILLIUM - else: - return None - -# Our transactions vendor -def vendor_name_to_internal_id(vendor_eatery_name): - vendor_eatery_name = ''.join(c.lower() for c in vendor_eatery_name if c.isalpha()) - if vendor_eatery_name == "bearnecessities": - return EateryID.BEAR_NECESSITIES - elif vendor_eatery_name == "northstarmarketplace": - return EateryID.NORTH_STAR_DINING - elif vendor_eatery_name == "jansensmarket": - return EateryID.JANSENS_MARKET - elif vendor_eatery_name == "stockinghallcafe" or vendor_eatery_name == "stockinghall": - return EateryID.DAIRY_BAR - elif vendor_eatery_name == "marthas": - return EateryID.MARTHAS_CAFE - elif vendor_eatery_name == "cafejennie": - return EateryID.CAFE_JENNIE - elif vendor_eatery_name == "goldiescafe": - return EateryID.GOLDIES_CAFE - elif vendor_eatery_name == "alicecookhouse": - return EateryID.COOK_HOUSE - elif vendor_eatery_name == "carlbeckerhouse": - return EateryID.BECKER_HOUSE - elif vendor_eatery_name == "duffield": - return EateryID.MATTINS_CAFE - elif vendor_eatery_name == "greendragon": - return EateryID.GREEN_DRAGON - elif vendor_eatery_name == "trillium": - return EateryID.TRILLIUM - elif vendor_eatery_name == "olinlibecafe": - return EateryID.LIBE_CAFE - elif vendor_eatery_name == "carolscafe": - return EateryID.CAROLS_CAFE - elif vendor_eatery_name == "statlerterrace": - return EateryID.TERRACE - elif vendor_eatery_name == "busstopbagels": - return EateryID.BUS_STOP_BAGELS - elif vendor_eatery_name == "kosher": - return EateryID.ONE_ZERO_FOUR_WEST - elif vendor_eatery_name == "jansensatbethehouse": - return EateryID.BETHE_HOUSE - elif vendor_eatery_name == "keetonhouse": - return EateryID.KEETON_HOUSE - elif vendor_eatery_name == "rpme": - return EateryID.RPCC - elif vendor_eatery_name == "rosehouse": - return EateryID.ROSE_HOUSE - elif vendor_eatery_name == "risley": - return EateryID.RISLEY - elif vendor_eatery_name == "frannysft": - return EateryID.FRANNYS - elif vendor_eatery_name == "mccormicks": - return EateryID.MCCORMICKS - elif vendor_eatery_name == "sage": - return EateryID.ATRIUM_CAFE - elif vendor_eatery_name == "straightmarket": - return EateryID.STRAIGHT_FROM_THE_MARKET - elif vendor_eatery_name == "crossingscafe": - return EateryID.CROSSINGS_CAFE - elif vendor_eatery_name == "okenshields": - return EateryID.OKENSHIELDS - elif vendor_eatery_name == "bigredbarn": - return EateryID.BIG_RED_BARN - elif vendor_eatery_name == "rustys": - return EateryID.RUSTYS - elif vendor_eatery_name == "manncafe": - return EateryID.MANN_CAFE - elif vendor_eatery_name == "statlermacs": - return EateryID.MACS_CAFE - else: - # TODO: Add a slack notif / flag that a wait time location was not recognized - return None \ No newline at end of file diff --git a/util/time.py b/util/time.py deleted file mode 100644 index 7fce11d..0000000 --- a/util/time.py +++ /dev/null @@ -1,5 +0,0 @@ -from datetime import date, time, datetime -import pytz - -def combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: - return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) From 15501b75a369824ed960796467c50a3f9fb18833 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Tue, 11 Jan 2022 10:10:26 -0500 Subject: [PATCH 047/305] ingest_db_snapshot typo fix --- src/eateries/management/commands/ingest_db_snapshot.py | 2 +- src/transactions/controllers/update_transactions_controller.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/eateries/management/commands/ingest_db_snapshot.py b/src/eateries/management/commands/ingest_db_snapshot.py index ff7a8a0..292928c 100644 --- a/src/eateries/management/commands/ingest_db_snapshot.py +++ b/src/eateries/management/commands/ingest_db_snapshot.py @@ -16,7 +16,7 @@ def ingest_data(self, serializer, file_name: SnapshotFileName): for line in file: if (len(line) > 2): json_objs.append(json.loads(line)) - serialized_objs = serializer(data=json~_objs, many=True) + serialized_objs = serializer(data=json_objs, many=True) serialized_objs.is_valid() serialized_objs.save() diff --git a/src/transactions/controllers/update_transactions_controller.py b/src/transactions/controllers/update_transactions_controller.py index 3c1245e..9f64e73 100644 --- a/src/transactions/controllers/update_transactions_controller.py +++ b/src/transactions/controllers/update_transactions_controller.py @@ -29,7 +29,7 @@ def process(self): try: TransactionHistory.objects.create(eatery_id = internal_id, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) except Exception as e: - print(e) + # print(e) num_inserted -= 1 return num_inserted From 7725e4cadc3c85c2df579cfd8d4a05a5da9aca8a Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Tue, 11 Jan 2022 10:21:34 -0500 Subject: [PATCH 048/305] If forcing reload, update the snapshot --- src/api/dfg/system/InMemoryCache.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/api/dfg/system/InMemoryCache.py b/src/api/dfg/system/InMemoryCache.py index afd6ea9..97617ec 100644 --- a/src/api/dfg/system/InMemoryCache.py +++ b/src/api/dfg/system/InMemoryCache.py @@ -51,12 +51,14 @@ def fifo_index(self): def __call__(self, *args, **kwargs): should_reload = kwargs.get("reload") + best_snapshot = None for snapshot in self.snapshots: if not should_reload and snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): - # print(f"{self}: Returning from cache") - return snapshot.get_data() + if best_snapshot == None or snapshot.recorded_time > best_snapshot.recorded_time: + best_snapshot = snapshot + if best_snapshot is not None: + return snapshot.get_data() - # print(f"{self}: Fetching data") new_snapshot = DataSnapshot(args, kwargs, self.child(*args, **kwargs), self.current_time()) if len(self.snapshots) < self.max_size: self.snapshots.append(new_snapshot) From c9986a949672aaddc8211cf46e1060bfffa1a126 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Tue, 11 Jan 2022 13:50:47 -0500 Subject: [PATCH 049/305] Add docker file and docker-compose.yml --- Dockerfile | 9 ++++---- docker-compose.yml | 22 +++++-------------- requirements.txt | 1 + src/api/datatype/Eatery.py | 4 ---- src/api/dfg/CornellDiningNow.py | 1 - src/api/dfg/EateriesFromDB.py | 1 - src/eateries/models.py | 1 - .../cornell_dining_now_eateries.json | 0 .../external_eateries.json | 0 9 files changed, 12 insertions(+), 27 deletions(-) rename {src/static_sources => static_sources}/cornell_dining_now_eateries.json (100%) rename {src/static_sources => static_sources}/external_eateries.json (100%) diff --git a/Dockerfile b/Dockerfile index a711a3c..0086eab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ FROM python:3.9 -WORKDIR /src -COPY requirements.txt /src/ -RUN pip install -r requirements.txt -COPY . /src/ \ No newline at end of file +RUN mkdir /usr/app +WORKDIR /usr/app +COPY ./src . +COPY ./requirements.txt . +RUN pip install -r requirements.txt \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 064fe6f..57b1406 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,20 +1,10 @@ +version: '3' + services: - web: + app: build: . env_file: .env - volumes: - - .:/src - web_make_migrations: - extends: - service: web - command: python manage.py makemigrations - web_migrate: - extends: - service: web - command: python manage.py migrate - web_run: - extends: - service: web - command: python manage.py runserver 0.0.0.0:8000 + command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:80" ports: - - "8000:8000" \ No newline at end of file + - "80:80" + restart: always \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d759c0b..a0381be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ certifi==2021.10.8 charset-normalizer==2.0.9 click==8.0.3 Django==4.0 +djangorestframework==3.13.1 google-api-core==2.3.2 google-api-python-client==2.33.0 google-auth==2.3.3 diff --git a/src/api/datatype/Eatery.py b/src/api/datatype/Eatery.py index 767987e..7332f0e 100644 --- a/src/api/datatype/Eatery.py +++ b/src/api/datatype/Eatery.py @@ -63,7 +63,6 @@ def __init__( longitude: Optional[float] = None, payment_methods: Optional[list[str]] = None, location: Optional[str] = None, - online_order: Optional[bool] = None, online_order_url: Optional[str] = None, wait_times: Optional[list[WaitTimesDay]] = None, alerts: Optional[list[EateryAlert]] = None @@ -78,7 +77,6 @@ def __init__( self.known_events = events self.payment_methods = payment_methods self.location = location - self.online_order = online_order self.online_order_url = online_order_url self.wait_times = wait_times self.alerts = alerts @@ -110,7 +108,6 @@ def to_json( "payment_methods": None if self.payment_methods is None else [payment_method for payment_method in self.payment_methods], "location": self.location, - "online_order": self.online_order, "online_order_url": self.online_order_url, "wait_times": None if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times], "alerts": None if self.alerts is None else [alert.to_json() for alert in self.alerts] @@ -131,7 +128,6 @@ def from_json(eatery_json): longitude=eatery_json.get("longitude"), payment_methods=eatery_json.get("payment_methods"), location=eatery_json.get("location"), - online_order=eatery_json.get("online_order"), online_order_url=eatery_json.get("online_order_url"), wait_times=None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]], diff --git a/src/api/dfg/CornellDiningNow.py b/src/api/dfg/CornellDiningNow.py index de104d7..b83168a 100644 --- a/src/api/dfg/CornellDiningNow.py +++ b/src/api/dfg/CornellDiningNow.py @@ -37,7 +37,6 @@ def parse_eatery(json_eatery: dict) -> Eatery: longitude=json_eatery["longitude"], payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), location=json_eatery["location"], - online_order=json_eatery["onlineOrdering"], online_order_url=json_eatery["onlineOrderUrl"] ) diff --git a/src/api/dfg/EateriesFromDB.py b/src/api/dfg/EateriesFromDB.py index d474e66..5ba6299 100644 --- a/src/api/dfg/EateriesFromDB.py +++ b/src/api/dfg/EateriesFromDB.py @@ -47,7 +47,6 @@ def eatery_from_serialized(serialized_eatery: dict, serialized_alerts: list[dict longitude=serialized_eatery["longitude"], payment_methods=EateriesFromDB.payment_methods(serialized_eatery), location=EateriesFromDB.none_repr(serialized_eatery["location"]), - online_order=serialized_eatery["online_order"], online_order_url=EateriesFromDB.none_repr(serialized_eatery["online_order_url"]), alerts = EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts) ) diff --git a/src/eateries/models.py b/src/eateries/models.py index de25063..6045b29 100644 --- a/src/eateries/models.py +++ b/src/eateries/models.py @@ -14,7 +14,6 @@ class CampusArea(models.TextChoices): image_url = models.URLField(blank=True) location = models.CharField(max_length=30, blank=True) campus_area = models.CharField(max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True) - online_order = models.BooleanField(null = True, blank=True) online_order_url = models.URLField(blank=True) latitude = models.FloatField(null = True, blank=True) longitude = models.FloatField(null = True, blank=True) diff --git a/src/static_sources/cornell_dining_now_eateries.json b/static_sources/cornell_dining_now_eateries.json similarity index 100% rename from src/static_sources/cornell_dining_now_eateries.json rename to static_sources/cornell_dining_now_eateries.json diff --git a/src/static_sources/external_eateries.json b/static_sources/external_eateries.json similarity index 100% rename from src/static_sources/external_eateries.json rename to static_sources/external_eateries.json From cc2c23fbe8320aa0333837bdc02d424dc11e64d4 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 13 Jan 2022 12:02:13 -0500 Subject: [PATCH 050/305] File restructuring, added reports endpoint --- src/api/migrations/0001_initial.py | 25 ---- src/api/migrations/__init__.py | 0 src/api/urls.py | 8 -- src/api/views.py | 86 -------------- src/{api => dfg}/__init__.py | 0 src/{api => dfg}/admin.py | 0 src/{api => dfg}/apps.py | 0 src/{api => dfg}/datatype/Eatery.py | 23 ++-- src/{api => dfg}/datatype/EateryAlert.py | 0 src/{api => dfg}/datatype/Event.py | 2 +- src/{api => dfg}/datatype/Menu.py | 2 +- src/{api => dfg}/datatype/MenuCategory.py | 2 +- src/{api => dfg}/datatype/MenuItem.py | 2 +- src/{api => dfg}/datatype/MenuItemSection.py | 2 +- src/{api => dfg}/datatype/MenuSubItem.py | 0 src/{api => dfg}/datatype/WaitTime.py | 0 src/{api => dfg}/datatype/WaitTimesDay.py | 0 .../dfg => dfg/nodes}/CornellDiningNow.py | 22 +--- src/{api/dfg => dfg/nodes}/DfgNode.py | 0 src/{api/dfg => dfg/nodes}/EateriesFromDB.py | 29 ++--- src/{api/dfg => dfg/nodes}/EateryStubs.py | 4 +- src/{api/dfg => dfg/nodes}/__init__.py | 0 .../dfg => dfg/nodes}/macros/EateryEvents.py | 16 +-- .../nodes}/macros/LeftMergeEateries.py | 8 +- .../nodes}/macros/LeftMergeEvents.py | 8 +- .../nodes}/schedule/CacheMenuInjection.py | 14 +-- .../nodes}/schedule/ClosedSchedule.py | 4 +- .../nodes}/schedule/CornellDiningEvents.py | 12 +- .../nodes}/schedule/DateSchedule.py | 4 +- .../nodes}/schedule/DayOfWeekSchedule.py | 6 +- .../nodes}/system/ConvertFromJson.py | 6 +- .../dfg => dfg/nodes}/system/ConvertToJson.py | 6 +- .../nodes}/system/DictResponseWrapper.py | 16 +-- .../nodes}/system/EateryGenerator.py | 4 +- .../dfg => dfg/nodes}/system/InMemoryCache.py | 4 +- .../dfg => dfg/nodes}/system/LeftMerge.py | 2 +- src/{api/dfg => dfg/nodes}/system/Mapping.py | 4 +- .../nodes}/wait_times/WaitTimeFilter.py | 13 ++- .../dfg => dfg/nodes}/wait_times/WaitTimes.py | 18 +-- src/{api => dfg}/tests.py | 0 src/dfg/views.py | 68 +++++++++++ src/eateries/admin.py | 4 +- src/eateries/controllers/create_report.py | 20 ++++ .../controllers/create_transaction.py} | 15 ++- .../controllers/delete_all_transactions.py | 8 ++ ...e_db_snapshot.py => export_db_snapshot.py} | 0 .../management/commands/ingest_db_snapshot.py | 2 +- .../commands/ingest_log_transactions.py | 13 ++- .../commands/ingest_recent_transactions.py} | 4 +- src/eateries/migrations/0001_initial.py | 98 ++++++++++++++-- .../0002_closedeventschedule_and_more.py | 27 ----- ...003_closedeventschedule_eatery_and_more.py | 26 ----- ...ayofweekeventschedule_dateeventschedule.py | 40 ------- ...ventschedule_event_description_and_more.py | 38 ------ ...temassociation_unique_together_and_more.py | 70 ----------- ...name_exceptionstore_alertstore_and_more.py | 21 ---- src/eateries/models.py | 110 ------------------ src/eateries/models/AlertModel.py | 9 ++ src/eateries/models/EateryModel.py | 22 ++++ src/eateries/models/EventScheduleModel.py | 47 ++++++++ src/eateries/models/MenuModel.py | 38 ++++++ src/eateries/models/ReportModel.py | 8 ++ .../models/TransactionModel.py} | 5 +- src/eateries/models/__init__.py | 9 ++ src/eateries/views.py | 18 +++ src/eatery_blue_backend/settings.py | 1 - src/eatery_blue_backend/urls.py | 10 +- src/transactions/__init__.py | 0 src/transactions/admin.py | 6 - src/transactions/apps.py | 6 - src/transactions/migrations/0001_initial.py | 34 ------ src/transactions/migrations/__init__.py | 0 src/transactions/tests.py | 3 - src/transactions/views.py | 3 - src/util/constants.py | 2 +- src/util/json.py | 41 +++++++ 76 files changed, 518 insertions(+), 660 deletions(-) delete mode 100644 src/api/migrations/0001_initial.py delete mode 100644 src/api/migrations/__init__.py delete mode 100644 src/api/urls.py delete mode 100644 src/api/views.py rename src/{api => dfg}/__init__.py (100%) rename src/{api => dfg}/admin.py (100%) rename src/{api => dfg}/apps.py (100%) rename src/{api => dfg}/datatype/Eatery.py (82%) rename src/{api => dfg}/datatype/EateryAlert.py (100%) rename src/{api => dfg}/datatype/Event.py (98%) rename src/{api => dfg}/datatype/Menu.py (87%) rename src/{api => dfg}/datatype/MenuCategory.py (92%) rename src/{api => dfg}/datatype/MenuItem.py (96%) rename src/{api => dfg}/datatype/MenuItemSection.py (91%) rename src/{api => dfg}/datatype/MenuSubItem.py (100%) rename src/{api => dfg}/datatype/WaitTime.py (100%) rename src/{api => dfg}/datatype/WaitTimesDay.py (100%) rename src/{api/dfg => dfg/nodes}/CornellDiningNow.py (62%) rename src/{api/dfg => dfg/nodes}/DfgNode.py (100%) rename src/{api/dfg => dfg/nodes}/EateriesFromDB.py (75%) rename src/{api/dfg => dfg/nodes}/EateryStubs.py (69%) rename src/{api/dfg => dfg/nodes}/__init__.py (100%) rename src/{api/dfg => dfg/nodes}/macros/EateryEvents.py (65%) rename src/{api/dfg => dfg/nodes}/macros/LeftMergeEateries.py (77%) rename src/{api/dfg => dfg/nodes}/macros/LeftMergeEvents.py (82%) rename src/{api/dfg => dfg/nodes}/schedule/CacheMenuInjection.py (92%) rename src/{api/dfg => dfg/nodes}/schedule/ClosedSchedule.py (83%) rename src/{api/dfg => dfg/nodes}/schedule/CornellDiningEvents.py (94%) rename src/{api/dfg => dfg/nodes}/schedule/DateSchedule.py (80%) rename src/{api/dfg => dfg/nodes}/schedule/DayOfWeekSchedule.py (92%) rename src/{api/dfg => dfg/nodes}/system/ConvertFromJson.py (92%) rename src/{api/dfg => dfg/nodes}/system/ConvertToJson.py (87%) rename src/{api/dfg => dfg/nodes}/system/DictResponseWrapper.py (56%) rename src/{api/dfg => dfg/nodes}/system/EateryGenerator.py (89%) rename src/{api/dfg => dfg/nodes}/system/InMemoryCache.py (96%) rename src/{api/dfg => dfg/nodes}/system/LeftMerge.py (98%) rename src/{api/dfg => dfg/nodes}/system/Mapping.py (84%) rename src/{api/dfg => dfg/nodes}/wait_times/WaitTimeFilter.py (76%) rename src/{api/dfg => dfg/nodes}/wait_times/WaitTimes.py (88%) rename src/{api => dfg}/tests.py (100%) create mode 100644 src/dfg/views.py create mode 100644 src/eateries/controllers/create_report.py rename src/{transactions/controllers/update_transactions_controller.py => eateries/controllers/create_transaction.py} (69%) create mode 100644 src/eateries/controllers/delete_all_transactions.py rename src/eateries/management/commands/{create_db_snapshot.py => export_db_snapshot.py} (100%) rename src/{transactions => eateries}/management/commands/ingest_log_transactions.py (69%) rename src/{transactions/management/commands/fetch_recent_transactions.py => eateries/management/commands/ingest_recent_transactions.py} (86%) delete mode 100644 src/eateries/migrations/0002_closedeventschedule_and_more.py delete mode 100644 src/eateries/migrations/0003_closedeventschedule_eatery_and_more.py delete mode 100644 src/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py delete mode 100644 src/eateries/migrations/0005_dateeventschedule_event_description_and_more.py delete mode 100644 src/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py delete mode 100644 src/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py delete mode 100644 src/eateries/models.py create mode 100644 src/eateries/models/AlertModel.py create mode 100644 src/eateries/models/EateryModel.py create mode 100644 src/eateries/models/EventScheduleModel.py create mode 100644 src/eateries/models/MenuModel.py create mode 100644 src/eateries/models/ReportModel.py rename src/{transactions/models.py => eateries/models/TransactionModel.py} (82%) create mode 100644 src/eateries/models/__init__.py delete mode 100644 src/transactions/__init__.py delete mode 100644 src/transactions/admin.py delete mode 100644 src/transactions/apps.py delete mode 100644 src/transactions/migrations/0001_initial.py delete mode 100644 src/transactions/migrations/__init__.py delete mode 100644 src/transactions/tests.py delete mode 100644 src/transactions/views.py create mode 100644 src/util/json.py diff --git a/src/api/migrations/0001_initial.py b/src/api/migrations/0001_initial.py deleted file mode 100644 index c6f14d5..0000000 --- a/src/api/migrations/0001_initial.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.0 on 2021-12-23 19:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='TransactionHistory', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('canonical_date', models.DateField()), - ('start_timestamp', models.TimeField()), - ('end_timestamp', models.TimeField()), - ('transaction_count', models.IntegerField()), - ], - ), - ] diff --git a/src/api/migrations/__init__.py b/src/api/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/api/urls.py b/src/api/urls.py deleted file mode 100644 index 4bf0e28..0000000 --- a/src/api/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.urls import path - -from . import views - -urlpatterns = [ - path("", views.index, name="index"), - path("gs", views.google_sheets_eateries, name="gs") -] diff --git a/src/api/views.py b/src/api/views.py deleted file mode 100644 index e44d9af..0000000 --- a/src/api/views.py +++ /dev/null @@ -1,86 +0,0 @@ -from datetime import date, timedelta - -import pytz -from django.http import JsonResponse -from api.datatype.Eatery import Eatery - -from api.dfg.CornellDiningNow import CornellDiningNow -from api.dfg.EateryStubs import EateryStubs -from api.dfg.EateriesFromDB import EateriesFromDB -from api.dfg.macros.EateryEvents import EateryEvents - -from api.dfg.system.DictResponseWrapper import DictResponseWrapper -from api.dfg.system.ConvertToJson import ConvertToJson -from api.dfg.system.EateryGenerator import EateryGenerator -from api.dfg.system.InMemoryCache import InMemoryCache -from api.dfg.system.Mapping import Mapping - -from api.dfg.macros.LeftMergeEateries import LeftMergeEateries - -from api.dfg.wait_times.WaitTimes import WaitTimes -from api.dfg.wait_times.WaitTimeFilter import WaitTimeFilter - -main_dfg = DictResponseWrapper( - ConvertToJson( - InMemoryCache( - WaitTimeFilter( - LeftMergeEateries( - Mapping( - child=EateryStubs(), - fn = lambda eatery, cache: EateryGenerator( - eatery_id=eatery.id, - wait_times_dfg=WaitTimes(eatery.id, cache) - ) - ), - LeftMergeEateries( - Mapping( - child = EateryStubs(), - fn = lambda eatery, cache: EateryGenerator( - eatery_id=eatery.id, - events_dfg=EateryEvents(eatery.id, cache) - ) - ), - LeftMergeEateries( - EateriesFromDB(), - LeftMergeEateries( - CornellDiningNow(), - EateryStubs() - ) - ) - ) - ) - ) - ) - ), - re_raise_exceptions=True -) - - -def index(request): - tzinfo = pytz.timezone("US/Eastern") - reload = request.GET.get('reload') - result = main_dfg( - tzinfo=tzinfo, - reload=reload is not None and reload != "false", - start=date.today(), - end=date.today() + timedelta(days=7) - ) - return JsonResponse(result) - - -def google_sheets_eateries(request): - # dfg = DictResponseWrapper( - # EateryToJson( - # GoogleSheetsEateries( - # spreadsheet_id="1ImfeTUA6I1Ub-aavgIW53Pf7EVB694f1294NPSCRd5c" - # ) - # ) - # ) - - # result = dfg( - # tzinfo=pytz.timezone("US/Eastern"), - # start=date.today(), - # end=date.today() + timedelta(days=7) - # ) - - return JsonResponse([]) diff --git a/src/api/__init__.py b/src/dfg/__init__.py similarity index 100% rename from src/api/__init__.py rename to src/dfg/__init__.py diff --git a/src/api/admin.py b/src/dfg/admin.py similarity index 100% rename from src/api/admin.py rename to src/dfg/admin.py diff --git a/src/api/apps.py b/src/dfg/apps.py similarity index 100% rename from src/api/apps.py rename to src/dfg/apps.py diff --git a/src/api/datatype/Eatery.py b/src/dfg/datatype/Eatery.py similarity index 82% rename from src/api/datatype/Eatery.py rename to src/dfg/datatype/Eatery.py index 7332f0e..5673746 100644 --- a/src/api/datatype/Eatery.py +++ b/src/dfg/datatype/Eatery.py @@ -3,10 +3,10 @@ from enum import Enum import pytz -from api.datatype.EateryAlert import EateryAlert +from dfg.datatype.EateryAlert import EateryAlert -from api.datatype.Event import Event, filter_range -from api.datatype.WaitTimesDay import WaitTimesDay +from dfg.datatype.Event import Event, filter_range +from dfg.datatype.WaitTimesDay import WaitTimesDay class EateryID(Enum): @@ -61,7 +61,9 @@ def __init__( events: Optional[list[Event]] = None, latitude: Optional[float] = None, longitude: Optional[float] = None, - payment_methods: Optional[list[str]] = None, + payment_accepts_cash: Optional[bool] = None, + payment_accepts_brbs: Optional[bool] = None, + payment_accepts_meal_swipes: Optional[bool] = None, location: Optional[str] = None, online_order_url: Optional[str] = None, wait_times: Optional[list[WaitTimesDay]] = None, @@ -75,7 +77,9 @@ def __init__( self.latitude = latitude self.longitude = longitude self.known_events = events - self.payment_methods = payment_methods + self.payment_accepts_cash = payment_accepts_cash + self.payment_accepts_brbs = payment_accepts_brbs + self.payment_accepts_meal_swipes = payment_accepts_meal_swipes self.location = location self.online_order_url = online_order_url self.wait_times = wait_times @@ -105,8 +109,9 @@ def to_json( else [event.to_json() for event in self.events(tzinfo, start, end)], "latitude": self.latitude, "longitude": self.longitude, - "payment_methods": None if self.payment_methods is None - else [payment_method for payment_method in self.payment_methods], + "payment_accepts_cash": self.payment_accepts_cash, + "payment_accepts_brbs": self.payment_accepts_brbs, + "payment_accepts_meal_swipes": self.payment_accepts_meal_swipes, "location": self.location, "online_order_url": self.online_order_url, "wait_times": None if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times], @@ -126,7 +131,9 @@ def from_json(eatery_json): else [Event.from_json(event) for event in eatery_json["events"]], latitude=eatery_json.get("latitude"), longitude=eatery_json.get("longitude"), - payment_methods=eatery_json.get("payment_methods"), + payment_accepts_cash=eatery_json.get("payment_accepts_cash"), + payment_accepts_brbs=eatery_json.get("payment_accepts_brbs"), + payment_accepts_meal_swipes=eatery_json.get("payment_accepts_meal_swipes"), location=eatery_json.get("location"), online_order_url=eatery_json.get("online_order_url"), wait_times=None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None diff --git a/src/api/datatype/EateryAlert.py b/src/dfg/datatype/EateryAlert.py similarity index 100% rename from src/api/datatype/EateryAlert.py rename to src/dfg/datatype/EateryAlert.py diff --git a/src/api/datatype/Event.py b/src/dfg/datatype/Event.py similarity index 98% rename from src/api/datatype/Event.py rename to src/dfg/datatype/Event.py index 9d3afcf..f0877b4 100644 --- a/src/api/datatype/Event.py +++ b/src/dfg/datatype/Event.py @@ -1,6 +1,6 @@ from typing import Optional from datetime import date, time -from api.datatype.Menu import Menu +from dfg.datatype.Menu import Menu from util.time import combined_timestamp import pytz diff --git a/src/api/datatype/Menu.py b/src/dfg/datatype/Menu.py similarity index 87% rename from src/api/datatype/Menu.py rename to src/dfg/datatype/Menu.py index c48e100..7fead2b 100644 --- a/src/api/datatype/Menu.py +++ b/src/dfg/datatype/Menu.py @@ -1,4 +1,4 @@ -from api.datatype.MenuCategory import MenuCategory +from dfg.datatype.MenuCategory import MenuCategory class Menu: diff --git a/src/api/datatype/MenuCategory.py b/src/dfg/datatype/MenuCategory.py similarity index 92% rename from src/api/datatype/MenuCategory.py rename to src/dfg/datatype/MenuCategory.py index f2cd1cc..0b34049 100644 --- a/src/api/datatype/MenuCategory.py +++ b/src/dfg/datatype/MenuCategory.py @@ -1,4 +1,4 @@ -from api.datatype.MenuItem import MenuItem +from dfg.datatype.MenuItem import MenuItem class MenuCategory: diff --git a/src/api/datatype/MenuItem.py b/src/dfg/datatype/MenuItem.py similarity index 96% rename from src/api/datatype/MenuItem.py rename to src/dfg/datatype/MenuItem.py index b611652..5fd48b3 100644 --- a/src/api/datatype/MenuItem.py +++ b/src/dfg/datatype/MenuItem.py @@ -1,6 +1,6 @@ from typing import Optional -from api.datatype.MenuItemSection import MenuItemSection +from dfg.datatype.MenuItemSection import MenuItemSection class MenuItem: diff --git a/src/api/datatype/MenuItemSection.py b/src/dfg/datatype/MenuItemSection.py similarity index 91% rename from src/api/datatype/MenuItemSection.py rename to src/dfg/datatype/MenuItemSection.py index 770c376..ba415f3 100644 --- a/src/api/datatype/MenuItemSection.py +++ b/src/dfg/datatype/MenuItemSection.py @@ -1,4 +1,4 @@ -from api.datatype.MenuSubItem import MenuSubItem +from dfg.datatype.MenuSubItem import MenuSubItem class MenuItemSection: diff --git a/src/api/datatype/MenuSubItem.py b/src/dfg/datatype/MenuSubItem.py similarity index 100% rename from src/api/datatype/MenuSubItem.py rename to src/dfg/datatype/MenuSubItem.py diff --git a/src/api/datatype/WaitTime.py b/src/dfg/datatype/WaitTime.py similarity index 100% rename from src/api/datatype/WaitTime.py rename to src/dfg/datatype/WaitTime.py diff --git a/src/api/datatype/WaitTimesDay.py b/src/dfg/datatype/WaitTimesDay.py similarity index 100% rename from src/api/datatype/WaitTimesDay.py rename to src/dfg/datatype/WaitTimesDay.py diff --git a/src/api/dfg/CornellDiningNow.py b/src/dfg/nodes/CornellDiningNow.py similarity index 62% rename from src/api/dfg/CornellDiningNow.py rename to src/dfg/nodes/CornellDiningNow.py index b83168a..7ae89c4 100644 --- a/src/api/dfg/CornellDiningNow.py +++ b/src/dfg/nodes/CornellDiningNow.py @@ -1,8 +1,8 @@ import requests -from api.dfg.DfgNode import DfgNode +from dfg.nodes.DfgNode import DfgNode -from api.datatype.Eatery import Eatery +from dfg.datatype.Eatery import Eatery from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL from datetime import date @@ -35,24 +35,12 @@ def parse_eatery(json_eatery: dict) -> Eatery: campus_area=json_eatery["campusArea"]["descrshort"], latitude=json_eatery["latitude"], longitude=json_eatery["longitude"], - payment_methods=CornellDiningNow.generate_payment_methods(json_eatery["payMethods"]), + payment_accepts_cash=True, + payment_accepts_brbs=any([method["descrshort"] == "Meal Plan - Debit" for method in json_eatery["payMethods"]]), + payment_accepts_meal_swipes=any([method["descrshort"] == "Meal Plan - Swipe" for method in json_eatery["payMethods"]]), location=json_eatery["location"], online_order_url=json_eatery["onlineOrderUrl"] ) - @staticmethod - def generate_payment_methods(json_paymethods: list): - payment_methods = [] - takes_cash = True - takes_brbs = any([method["descrshort"] == "Meal Plan - Debit" for method in json_paymethods]) - takes_swipes = any([method["descrshort"] == "Meal Plan - Swipe" for method in json_paymethods]) - if takes_cash: - payment_methods.append("cash") - if takes_brbs: - payment_methods.append("brbs") - if takes_swipes: - payment_methods.append("swipes") - return payment_methods - def description(self): return "CornellDiningNow" diff --git a/src/api/dfg/DfgNode.py b/src/dfg/nodes/DfgNode.py similarity index 100% rename from src/api/dfg/DfgNode.py rename to src/dfg/nodes/DfgNode.py diff --git a/src/api/dfg/EateriesFromDB.py b/src/dfg/nodes/EateriesFromDB.py similarity index 75% rename from src/api/dfg/EateriesFromDB.py rename to src/dfg/nodes/EateriesFromDB.py index 5ba6299..d149083 100644 --- a/src/api/dfg/EateriesFromDB.py +++ b/src/dfg/nodes/EateriesFromDB.py @@ -4,13 +4,13 @@ import pytz -from api.datatype.Eatery import Eatery, EateryID -from api.datatype.EateryAlert import EateryAlert -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem -from api.dfg.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery, EateryID +from dfg.datatype.EateryAlert import EateryAlert +from dfg.datatype.Event import Event +from dfg.datatype.Menu import Menu +from dfg.datatype.MenuCategory import MenuCategory +from dfg.datatype.MenuItem import MenuItem +from dfg.nodes.DfgNode import DfgNode from eateries.models import EateryStore, AlertStore from eateries.serializers import EateryStoreSerializer, AlertStoreSerializer @@ -45,22 +45,13 @@ def eatery_from_serialized(serialized_eatery: dict, serialized_alerts: list[dict events=None, latitude=serialized_eatery["latitude"], longitude=serialized_eatery["longitude"], - payment_methods=EateriesFromDB.payment_methods(serialized_eatery), + payment_accepts_cash=serialized_eatery["payment_accepts_cash"], + payment_accepts_brbs=serialized_eatery["payment_accepts_brbs"], + payment_accepts_meal_swipes=serialized_eatery["payment_accepts_meal_swipes"], location=EateriesFromDB.none_repr(serialized_eatery["location"]), online_order_url=EateriesFromDB.none_repr(serialized_eatery["online_order_url"]), alerts = EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts) ) - - @staticmethod - def payment_methods(serialized_eatery:dict): - pay_methods = [] - if serialized_eatery["payment_accepts_cash"]: - pay_methods.append("cash") - if serialized_eatery["payment_accepts_brbs"]: - pay_methods.append("brbs") - if serialized_eatery["payment_accepts_meal_swipes"]: - pay_methods.append("swipes") - return pay_methods @staticmethod def alerts(eatery_id: int, serialized_alerts: list[dict]): diff --git a/src/api/dfg/EateryStubs.py b/src/dfg/nodes/EateryStubs.py similarity index 69% rename from src/api/dfg/EateryStubs.py rename to src/dfg/nodes/EateryStubs.py index 4b1d4c6..04b0561 100644 --- a/src/api/dfg/EateryStubs.py +++ b/src/dfg/nodes/EateryStubs.py @@ -1,5 +1,5 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery, EateryID class EateryStubs(DfgNode): diff --git a/src/api/dfg/__init__.py b/src/dfg/nodes/__init__.py similarity index 100% rename from src/api/dfg/__init__.py rename to src/dfg/nodes/__init__.py diff --git a/src/api/dfg/macros/EateryEvents.py b/src/dfg/nodes/macros/EateryEvents.py similarity index 65% rename from src/api/dfg/macros/EateryEvents.py rename to src/dfg/nodes/macros/EateryEvents.py index 26c7079..babdc76 100644 --- a/src/api/dfg/macros/EateryEvents.py +++ b/src/dfg/nodes/macros/EateryEvents.py @@ -1,14 +1,14 @@ -from api.dfg.DfgNode import DfgNode +from dfg.nodes.DfgNode import DfgNode -from api.dfg.schedule.ClosedSchedule import ClosedSchedule -from api.dfg.schedule.DayOfWeekSchedule import DayOfWeekSchedule -from api.dfg.schedule.DateSchedule import DateSchedule -from api.dfg.schedule.CornellDiningEvents import CornellDiningEvents +from dfg.nodes.schedule.ClosedSchedule import ClosedSchedule +from dfg.nodes.schedule.DayOfWeekSchedule import DayOfWeekSchedule +from dfg.nodes.schedule.DateSchedule import DateSchedule +from dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents -from api.dfg.macros.LeftMergeEvents import LeftMergeEvents +from dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents -from api.datatype.Eatery import EateryID -from api.dfg.schedule.CacheMenuInjection import CacheMenuInjection +from dfg.datatype.Eatery import EateryID +from dfg.nodes.schedule.CacheMenuInjection import CacheMenuInjection # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if # conflict) diff --git a/src/api/dfg/macros/LeftMergeEateries.py b/src/dfg/nodes/macros/LeftMergeEateries.py similarity index 77% rename from src/api/dfg/macros/LeftMergeEateries.py rename to src/dfg/nodes/macros/LeftMergeEateries.py index 92f5e87..9eb0bf6 100644 --- a/src/api/dfg/macros/LeftMergeEateries.py +++ b/src/dfg/nodes/macros/LeftMergeEateries.py @@ -1,7 +1,7 @@ -from api.dfg.DfgNode import DfgNode -from api.dfg.system.ConvertToJson import ConvertToJson -from api.dfg.system.ConvertFromJson import EateryFromJson -from api.dfg.system.LeftMerge import LeftMerge +from dfg.nodes.DfgNode import DfgNode +from dfg.nodes.system.ConvertToJson import ConvertToJson +from dfg.nodes.system.ConvertFromJson import EateryFromJson +from dfg.nodes.system.LeftMerge import LeftMerge class LeftMergeEateries(DfgNode): diff --git a/src/api/dfg/macros/LeftMergeEvents.py b/src/dfg/nodes/macros/LeftMergeEvents.py similarity index 82% rename from src/api/dfg/macros/LeftMergeEvents.py rename to src/dfg/nodes/macros/LeftMergeEvents.py index 264f940..26bfc8f 100644 --- a/src/api/dfg/macros/LeftMergeEvents.py +++ b/src/dfg/nodes/macros/LeftMergeEvents.py @@ -1,8 +1,8 @@ -from api.dfg.DfgNode import DfgNode +from dfg.nodes.DfgNode import DfgNode -from api.dfg.system.ConvertToJson import ConvertToJson -from api.dfg.system.ConvertFromJson import EventFromJson -from api.dfg.system.LeftMerge import LeftMerge +from dfg.nodes.system.ConvertToJson import ConvertToJson +from dfg.nodes.system.ConvertFromJson import EventFromJson +from dfg.nodes.system.LeftMerge import LeftMerge # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if # conflict) diff --git a/src/api/dfg/schedule/CacheMenuInjection.py b/src/dfg/nodes/schedule/CacheMenuInjection.py similarity index 92% rename from src/api/dfg/schedule/CacheMenuInjection.py rename to src/dfg/nodes/schedule/CacheMenuInjection.py index a339102..cdab22c 100644 --- a/src/api/dfg/schedule/CacheMenuInjection.py +++ b/src/dfg/nodes/schedule/CacheMenuInjection.py @@ -1,10 +1,10 @@ -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem -from api.datatype.MenuItemSection import MenuItemSection -from api.datatype.MenuSubItem import MenuSubItem -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID +from dfg.datatype.Menu import Menu +from dfg.datatype.MenuCategory import MenuCategory +from dfg.datatype.MenuItem import MenuItem +from dfg.datatype.MenuItemSection import MenuItemSection +from dfg.datatype.MenuSubItem import MenuSubItem +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery, EateryID from eateries.models import CategoryItemAssociation, SubItemStore class CacheMenuInjection(DfgNode): diff --git a/src/api/dfg/schedule/ClosedSchedule.py b/src/dfg/nodes/schedule/ClosedSchedule.py similarity index 83% rename from src/api/dfg/schedule/ClosedSchedule.py rename to src/dfg/nodes/schedule/ClosedSchedule.py index 31d4fc2..766456f 100644 --- a/src/api/dfg/schedule/ClosedSchedule.py +++ b/src/dfg/nodes/schedule/ClosedSchedule.py @@ -1,5 +1,5 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery, EateryID class ClosedSchedule(DfgNode): diff --git a/src/api/dfg/schedule/CornellDiningEvents.py b/src/dfg/nodes/schedule/CornellDiningEvents.py similarity index 94% rename from src/api/dfg/schedule/CornellDiningEvents.py rename to src/dfg/nodes/schedule/CornellDiningEvents.py index 9bfc206..ec3d28d 100644 --- a/src/api/dfg/schedule/CornellDiningEvents.py +++ b/src/dfg/nodes/schedule/CornellDiningEvents.py @@ -1,9 +1,9 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID -from api.datatype.Event import Event -from api.datatype.Menu import Menu -from api.datatype.MenuCategory import MenuCategory -from api.datatype.MenuItem import MenuItem +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery, EateryID +from dfg.datatype.Event import Event +from dfg.datatype.Menu import Menu +from dfg.datatype.MenuCategory import MenuCategory +from dfg.datatype.MenuItem import MenuItem from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL from datetime import date diff --git a/src/api/dfg/schedule/DateSchedule.py b/src/dfg/nodes/schedule/DateSchedule.py similarity index 80% rename from src/api/dfg/schedule/DateSchedule.py rename to src/dfg/nodes/schedule/DateSchedule.py index 738eaa0..a933d70 100644 --- a/src/api/dfg/schedule/DateSchedule.py +++ b/src/dfg/nodes/schedule/DateSchedule.py @@ -1,5 +1,5 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery, EateryID # from eateries.models import DateEventSchedule class DateSchedule(DfgNode): diff --git a/src/api/dfg/schedule/DayOfWeekSchedule.py b/src/dfg/nodes/schedule/DayOfWeekSchedule.py similarity index 92% rename from src/api/dfg/schedule/DayOfWeekSchedule.py rename to src/dfg/nodes/schedule/DayOfWeekSchedule.py index 2966806..9cd586b 100644 --- a/src/api/dfg/schedule/DayOfWeekSchedule.py +++ b/src/dfg/nodes/schedule/DayOfWeekSchedule.py @@ -1,7 +1,7 @@ -from api.datatype.Event import Event -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID +from dfg.datatype.Event import Event +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery, EateryID from eateries.models import DayOfWeekEventSchedule from datetime import timedelta, datetime from util.time import combined_timestamp diff --git a/src/api/dfg/system/ConvertFromJson.py b/src/dfg/nodes/system/ConvertFromJson.py similarity index 92% rename from src/api/dfg/system/ConvertFromJson.py rename to src/dfg/nodes/system/ConvertFromJson.py index b85f28f..a610e99 100644 --- a/src/api/dfg/system/ConvertFromJson.py +++ b/src/dfg/nodes/system/ConvertFromJson.py @@ -1,8 +1,8 @@ from typing import Union -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery -from api.datatype.Event import Event +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery +from dfg.datatype.Event import Event class EateryFromJson(DfgNode): diff --git a/src/api/dfg/system/ConvertToJson.py b/src/dfg/nodes/system/ConvertToJson.py similarity index 87% rename from src/api/dfg/system/ConvertToJson.py rename to src/dfg/nodes/system/ConvertToJson.py index 25129f0..dbc5eab 100644 --- a/src/api/dfg/system/ConvertToJson.py +++ b/src/dfg/nodes/system/ConvertToJson.py @@ -1,8 +1,8 @@ from typing import Union -from api.datatype.Event import Event +from dfg.datatype.Event import Event -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery class ConvertToJson(DfgNode): diff --git a/src/api/dfg/system/DictResponseWrapper.py b/src/dfg/nodes/system/DictResponseWrapper.py similarity index 56% rename from src/api/dfg/system/DictResponseWrapper.py rename to src/dfg/nodes/system/DictResponseWrapper.py index 618d89b..7001388 100644 --- a/src/api/dfg/system/DictResponseWrapper.py +++ b/src/dfg/nodes/system/DictResponseWrapper.py @@ -1,4 +1,5 @@ -from api.dfg.DfgNode import DfgNode +from dfg.nodes.DfgNode import DfgNode +from util.json import success_json, error_json class DictResponseWrapper(DfgNode): @@ -9,21 +10,12 @@ def __init__(self, child: DfgNode, re_raise_exceptions: bool = False): def __call__(self, *args, **kwargs): try: - return { - "success": True, - "data": self.child(*args, **kwargs), - "error": None - } + return success_json(self.child(*args, **kwargs)) except Exception as e: if self.re_raise_exceptions: raise e - - return { - "success": False, - "data": None, - "error": str(e) - } + return error_json(str(e)) def description(self): return "DictResponseWrapper" diff --git a/src/api/dfg/system/EateryGenerator.py b/src/dfg/nodes/system/EateryGenerator.py similarity index 89% rename from src/api/dfg/system/EateryGenerator.py rename to src/dfg/nodes/system/EateryGenerator.py index 7cbb6dc..53e0df6 100644 --- a/src/api/dfg/system/EateryGenerator.py +++ b/src/dfg/nodes/system/EateryGenerator.py @@ -1,5 +1,5 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery, EateryID from typing import Optional class EateryGenerator(DfgNode): diff --git a/src/api/dfg/system/InMemoryCache.py b/src/dfg/nodes/system/InMemoryCache.py similarity index 96% rename from src/api/dfg/system/InMemoryCache.py rename to src/dfg/nodes/system/InMemoryCache.py index 97617ec..f204a6b 100644 --- a/src/api/dfg/system/InMemoryCache.py +++ b/src/dfg/nodes/system/InMemoryCache.py @@ -1,9 +1,9 @@ import time -from api.dfg.DfgNode import DfgNode +from dfg.nodes.DfgNode import DfgNode from typing import Optional -from api.dfg.system.ConvertToJson import ConvertToJson +from dfg.nodes.system.ConvertToJson import ConvertToJson class DataSnapshot: diff --git a/src/api/dfg/system/LeftMerge.py b/src/dfg/nodes/system/LeftMerge.py similarity index 98% rename from src/api/dfg/system/LeftMerge.py rename to src/dfg/nodes/system/LeftMerge.py index 7d47366..6b5170c 100644 --- a/src/api/dfg/system/LeftMerge.py +++ b/src/dfg/nodes/system/LeftMerge.py @@ -1,4 +1,4 @@ -from api.dfg.DfgNode import DfgNode +from dfg.nodes.DfgNode import DfgNode from typing import Callable, TypeVar, Any from functools import cmp_to_key T = TypeVar("T") diff --git a/src/api/dfg/system/Mapping.py b/src/dfg/nodes/system/Mapping.py similarity index 84% rename from src/api/dfg/system/Mapping.py rename to src/dfg/nodes/system/Mapping.py index 95539e3..bd7aea2 100644 --- a/src/api/dfg/system/Mapping.py +++ b/src/dfg/nodes/system/Mapping.py @@ -1,5 +1,5 @@ -from api.dfg.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID +from dfg.nodes.DfgNode import DfgNode +from dfg.datatype.Eatery import Eatery, EateryID from typing import Callable, Any class Mapping(DfgNode): diff --git a/src/api/dfg/wait_times/WaitTimeFilter.py b/src/dfg/nodes/wait_times/WaitTimeFilter.py similarity index 76% rename from src/api/dfg/wait_times/WaitTimeFilter.py rename to src/dfg/nodes/wait_times/WaitTimeFilter.py index ae2bac0..66ff36a 100644 --- a/src/api/dfg/wait_times/WaitTimeFilter.py +++ b/src/dfg/nodes/wait_times/WaitTimeFilter.py @@ -1,5 +1,5 @@ -from api.datatype.WaitTimesDay import WaitTimesDay -from api.dfg.DfgNode import DfgNode +from dfg.datatype.WaitTimesDay import WaitTimesDay +from dfg.nodes.DfgNode import DfgNode # Removes all wait times that are not part of the eatery's events @@ -25,10 +25,11 @@ def __call__(self, *args, **kwargs): eatery_events = eatery.events() if any([wait_time_data.timestamp in event for event in eatery_events]): filtered_data.append(wait_time_data) - wait_times_filtered.append(WaitTimesDay( - canonical_date=day_wait_times.canonical_date, - data=filtered_data - )) + if len(filtered_data) > 0: + wait_times_filtered.append(WaitTimesDay( + canonical_date=day_wait_times.canonical_date, + data=filtered_data + )) eatery_clone = eatery.clone() eatery_clone.wait_times = wait_times_filtered result.append(eatery_clone) diff --git a/src/api/dfg/wait_times/WaitTimes.py b/src/dfg/nodes/wait_times/WaitTimes.py similarity index 88% rename from src/api/dfg/wait_times/WaitTimes.py rename to src/dfg/nodes/wait_times/WaitTimes.py index 369a9e6..f8a834a 100644 --- a/src/api/dfg/wait_times/WaitTimes.py +++ b/src/dfg/nodes/wait_times/WaitTimes.py @@ -3,12 +3,12 @@ import pytz from django.db.models import Avg -from api.datatype.Eatery import Eatery, EateryID -from api.datatype.Event import Event -from api.datatype.WaitTime import WaitTime -from api.datatype.WaitTimesDay import WaitTimesDay -from api.dfg.DfgNode import DfgNode -from transactions.models import TransactionHistory +from dfg.datatype.Eatery import Eatery, EateryID +from dfg.datatype.Event import Event +from dfg.datatype.WaitTime import WaitTime +from dfg.datatype.WaitTimesDay import WaitTimesDay +from dfg.nodes.DfgNode import DfgNode +from eateries.models import TransactionHistoryStore from util.time import combined_timestamp @@ -27,7 +27,7 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: past_days = [] for i in range(1, 13): past_days.append(date - timedelta(days=7*i)) - transaction_avg_counts = TransactionHistory.objects.filter(canonical_date__in=past_days) \ + transaction_avg_counts = TransactionHistoryStore.objects.filter(canonical_date__in=past_days) \ .values("eatery_id", "block_end_time") \ .annotate(transaction_avg=Avg("transaction_count")) for unit in transaction_avg_counts: @@ -38,7 +38,9 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: eatery_wait_times = [] for date in self.cache["transactions"]: eatery_transaction_avgs = [transaction_avg for transaction_avg in self.cache["transactions"][date] if transaction_avg["eatery_id"] == self.eatery_id.value] - eatery_wait_times.append(WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs, kwargs.get("tzinfo"))) + date_wait_times = WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs, kwargs.get("tzinfo")) + if date_wait_times is not None: + eatery_wait_times.append(date_wait_times) return eatery_wait_times diff --git a/src/api/tests.py b/src/dfg/tests.py similarity index 100% rename from src/api/tests.py rename to src/dfg/tests.py diff --git a/src/dfg/views.py b/src/dfg/views.py new file mode 100644 index 0000000..8fa2101 --- /dev/null +++ b/src/dfg/views.py @@ -0,0 +1,68 @@ +from datetime import date, timedelta + +import pytz +from django.http import JsonResponse +from rest_framework.views import APIView + +from dfg.nodes.CornellDiningNow import CornellDiningNow +from dfg.nodes.EateryStubs import EateryStubs +from dfg.nodes.EateriesFromDB import EateriesFromDB +from dfg.nodes.macros.EateryEvents import EateryEvents + +from dfg.nodes.system.DictResponseWrapper import DictResponseWrapper +from dfg.nodes.system.ConvertToJson import ConvertToJson +from dfg.nodes.system.EateryGenerator import EateryGenerator +from dfg.nodes.system.InMemoryCache import InMemoryCache +from dfg.nodes.system.Mapping import Mapping + +from dfg.nodes.macros.LeftMergeEateries import LeftMergeEateries + +from dfg.nodes.wait_times.WaitTimes import WaitTimes +from dfg.nodes.wait_times.WaitTimeFilter import WaitTimeFilter + +class MainDfgView(APIView): + main_dfg = DictResponseWrapper( + ConvertToJson( + InMemoryCache( + WaitTimeFilter( + LeftMergeEateries( + Mapping( + child=EateryStubs(), + fn = lambda eatery, cache: EateryGenerator( + eatery_id=eatery.id, + wait_times_dfg=WaitTimes(eatery.id, cache) + ) + ), + LeftMergeEateries( + Mapping( + child = EateryStubs(), + fn = lambda eatery, cache: EateryGenerator( + eatery_id=eatery.id, + events_dfg=EateryEvents(eatery.id, cache) + ) + ), + LeftMergeEateries( + EateriesFromDB(), + LeftMergeEateries( + CornellDiningNow(), + EateryStubs() + ) + ) + ) + ) + ) + ) + ), + re_raise_exceptions=True + ) + + def get(self, request): + tzinfo = pytz.timezone("US/Eastern") + reload = request.GET.get('reload') + result = self.main_dfg( + tzinfo=tzinfo, + reload=reload is not None and reload != "false", + start=date.today(), + end=date.today() + timedelta(days=7) + ) + return JsonResponse(result) \ No newline at end of file diff --git a/src/eateries/admin.py b/src/eateries/admin.py index 1a1a610..3f75c02 100644 --- a/src/eateries/admin.py +++ b/src/eateries/admin.py @@ -12,4 +12,6 @@ admin.site.register(models.CategoryItemAssociation) admin.site.register(models.DayOfWeekEventSchedule) admin.site.register(models.DateEventSchedule) -admin.site.register(models.ClosedEventSchedule) \ No newline at end of file +admin.site.register(models.ClosedEventSchedule) +admin.site.register(models.TransactionHistoryStore) +admin.site.register(models.ReportStore) \ No newline at end of file diff --git a/src/eateries/controllers/create_report.py b/src/eateries/controllers/create_report.py new file mode 100644 index 0000000..3ccd4af --- /dev/null +++ b/src/eateries/controllers/create_report.py @@ -0,0 +1,20 @@ +from datetime import datetime +from dfg.datatype.Eatery import EateryID +from eateries.models import ReportStore + +class CreateReportController: + + def __init__(self, eatery_id: EateryID, type: str, content: str): + self.eatery_id = eatery_id + self.type = type + self.content = content + + def process(self): + current_timestamp = datetime.now().timestamp() + ReportStore.objects.create( + eatery_id = self.eatery_id.value, + type = self.type, + content = self.content, + created_timestamp = current_timestamp + ) + \ No newline at end of file diff --git a/src/transactions/controllers/update_transactions_controller.py b/src/eateries/controllers/create_transaction.py similarity index 69% rename from src/transactions/controllers/update_transactions_controller.py rename to src/eateries/controllers/create_transaction.py index 9f64e73..2e41e1a 100644 --- a/src/transactions/controllers/update_transactions_controller.py +++ b/src/eateries/controllers/create_transaction.py @@ -1,9 +1,9 @@ from datetime import datetime, timedelta -from transactions.models import TransactionHistory +from eateries.models import TransactionHistoryStore from util.constants import vendor_name_to_internal_id - import pytz -class UpdateTransactionsController: + +class CreateTransactionController: def __init__(self, data): self._data = data @@ -16,7 +16,7 @@ def process(self): canonical_date = recent_datetime.date() block_end_time = recent_datetime.time() if recent_datetime.hour < 4: - # between 12am and 4am associate this with the previous day + # between 12am and 4am associate this transaction with the previous day canonical_date = canonical_date - timedelta(days=1) num_inserted = 0 ignored_names = set() @@ -27,7 +27,12 @@ def process(self): else: num_inserted += 1 try: - TransactionHistory.objects.create(eatery_id = internal_id, canonical_date = canonical_date, block_end_time = block_end_time, transaction_count=place["CROWD_COUNT"]) + TransactionHistoryStore.objects.create( + eatery_id = internal_id, + canonical_date = canonical_date, + block_end_time = block_end_time, + transaction_count=place["CROWD_COUNT"] + ) except Exception as e: # print(e) num_inserted -= 1 diff --git a/src/eateries/controllers/delete_all_transactions.py b/src/eateries/controllers/delete_all_transactions.py new file mode 100644 index 0000000..a0cf85d --- /dev/null +++ b/src/eateries/controllers/delete_all_transactions.py @@ -0,0 +1,8 @@ + +from eateries.models import TransactionHistoryStore + +class DeleteAllTransactionsController: + + def process(self): + return TransactionHistoryStore.objects.all().delete()[0] + \ No newline at end of file diff --git a/src/eateries/management/commands/create_db_snapshot.py b/src/eateries/management/commands/export_db_snapshot.py similarity index 100% rename from src/eateries/management/commands/create_db_snapshot.py rename to src/eateries/management/commands/export_db_snapshot.py diff --git a/src/eateries/management/commands/ingest_db_snapshot.py b/src/eateries/management/commands/ingest_db_snapshot.py index 292928c..6fdc9b2 100644 --- a/src/eateries/management/commands/ingest_db_snapshot.py +++ b/src/eateries/management/commands/ingest_db_snapshot.py @@ -10,7 +10,7 @@ class Command(BaseCommand): # Only writes data if the table has been flushed def ingest_data(self, serializer, file_name: SnapshotFileName): - folder_path = "db_snapshots/2022-01-10 13:05:44" + folder_path = "/Users/connorreinhold/Documents/AppDev/new-eatery/eatery-blue-backend/src/db_snapshots/2022-01-11 15:18:05" with open(f"{folder_path}/{file_name.value}", "r") as file: json_objs = [] for line in file: diff --git a/src/transactions/management/commands/ingest_log_transactions.py b/src/eateries/management/commands/ingest_log_transactions.py similarity index 69% rename from src/transactions/management/commands/ingest_log_transactions.py rename to src/eateries/management/commands/ingest_log_transactions.py index 82e713e..f6bbb8b 100644 --- a/src/transactions/management/commands/ingest_log_transactions.py +++ b/src/eateries/management/commands/ingest_log_transactions.py @@ -4,16 +4,17 @@ from datetime import datetime from django.core.management.base import BaseCommand -from transactions.controllers.update_transactions_controller import UpdateTransactionsController -from transactions.models import TransactionHistory +from eateries.controllers.create_transaction import CreateTransactionController +from eateries.controllers.delete_all_transactions import DeleteAllTransactionsController + class Command(BaseCommand): - help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistory table' + help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistoryStore table' def handle(self, *args, **options): - num_deleted = TransactionHistory.objects.all().delete()[0] + num_deleted = DeleteAllTransactionsController().process() counter = 0 num_inserted = 0 - with open("static_sources/data.log", "r") as log: + with open("../static_sources/data.log", "r") as log: for line in log: try: data = json.loads(line) @@ -22,7 +23,7 @@ def handle(self, *args, **options): print(timestamp) if timestamp.year == 2021 and timestamp.month > 7: counter += 1 - inserted = UpdateTransactionsController(data).process() + inserted = CreateTransactionController(data).process() num_inserted += inserted except Exception as e: pass diff --git a/src/transactions/management/commands/fetch_recent_transactions.py b/src/eateries/management/commands/ingest_recent_transactions.py similarity index 86% rename from src/transactions/management/commands/fetch_recent_transactions.py rename to src/eateries/management/commands/ingest_recent_transactions.py index b264bf9..7a6f2ed 100644 --- a/src/transactions/management/commands/fetch_recent_transactions.py +++ b/src/eateries/management/commands/ingest_recent_transactions.py @@ -4,7 +4,7 @@ import os from requests.structures import CaseInsensitiveDict from django.core.management.base import BaseCommand -from transactions.controllers.update_transactions_controller import UpdateTransactionsController +from eateries.controllers.create_transaction import CreateTransactionController class Command(BaseCommand): help = 'Fetches transaction data from a vendor API and adds it to our transaction history database' @@ -20,7 +20,7 @@ def handle(self, *args, **options): resp = requests.get(endpoint, headers=headers) num_inserted = 0 if resp.status_code == 200: - res = UpdateTransactionsController(resp.json()).process() + res = CreateTransactionController(resp.json()).process() if res["success"]: num_inserted = res["result"]["num_inserted"] # print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file diff --git a/src/eateries/migrations/0001_initial.py b/src/eateries/migrations/0001_initial.py index a571daa..25223e4 100644 --- a/src/eateries/migrations/0001_initial.py +++ b/src/eateries/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2022-01-10 17:56 +# Generated by Django 4.0 on 2022-01-13 15:03 from django.db import migrations, models import django.db.models.deletion @@ -21,7 +21,6 @@ class Migration(migrations.Migration): ('image_url', models.URLField(blank=True)), ('location', models.CharField(blank=True, max_length=30)), ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), - ('online_order', models.BooleanField(blank=True, null=True)), ('online_order_url', models.URLField(blank=True)), ('latitude', models.FloatField(blank=True, null=True)), ('longitude', models.FloatField(blank=True, null=True)), @@ -33,7 +32,7 @@ class Migration(migrations.Migration): migrations.CreateModel( name='ItemStore', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.IntegerField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=40)), ('description', models.CharField(blank=True, max_length=200)), ('base_price', models.FloatField(blank=True, null=True)), @@ -41,9 +40,19 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='SubItemStore', + name='TransactionHistoryStore', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('canonical_date', models.DateField()), + ('block_end_time', models.TimeField()), + ('transaction_count', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.CreateModel( + name='SubItemStore', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), ('additional_price', models.FloatField(blank=True, null=True)), ('total_price', models.FloatField(blank=True, null=True)), ('name', models.CharField(max_length=40)), @@ -52,27 +61,66 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='MenuStore', + name='ReportStore', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(max_length=200)), + ('content', models.TextField()), + ('created_timestamp', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.CreateModel( + name='MenuStore', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=40)), ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), ], ), migrations.CreateModel( - name='ExceptionStore', + name='DayOfWeekEventSchedule', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('description', models.CharField(max_length=250)), + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('event_description', models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], max_length=10)), + ('day_of_week', models.CharField(choices=[('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday')], max_length=10)), + ('start', models.TimeField()), + ('end', models.TimeField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ], + ), + migrations.CreateModel( + name='DateEventSchedule', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('event_description', models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], max_length=10)), + ('canonical_date', models.DateField()), ('start_timestamp', models.IntegerField()), ('end_timestamp', models.IntegerField()), ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ClosedEventSchedule', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('event_description', models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], max_length=10)), + ('canonical_date', models.DateField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + options={ + 'abstract': False, + }, ), migrations.CreateModel( name='CategoryStore', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.IntegerField(primary_key=True, serialize=False)), ('category', models.CharField(max_length=40)), ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), ], @@ -80,9 +128,39 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CategoryItemAssociation', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.IntegerField(primary_key=True, serialize=False)), ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.categorystore')), ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), ], ), + migrations.CreateModel( + name='AlertStore', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('description', models.CharField(max_length=250)), + ('start_timestamp', models.IntegerField()), + ('end_timestamp', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ], + ), + migrations.AddIndex( + model_name='transactionhistorystore', + index=models.Index(fields=['canonical_date'], name='eateries_tr_canonic_31ef81_idx'), + ), + migrations.AlterUniqueTogether( + name='transactionhistorystore', + unique_together={('eatery_id', 'block_end_time', 'canonical_date')}, + ), + migrations.AlterUniqueTogether( + name='menustore', + unique_together={('eatery', 'name')}, + ), + migrations.AlterUniqueTogether( + name='dayofweekeventschedule', + unique_together={('eatery', 'day_of_week', 'event_description')}, + ), + migrations.AlterUniqueTogether( + name='categorystore', + unique_together={('menu', 'category')}, + ), ] diff --git a/src/eateries/migrations/0002_closedeventschedule_and_more.py b/src/eateries/migrations/0002_closedeventschedule_and_more.py deleted file mode 100644 index 358bf34..0000000 --- a/src/eateries/migrations/0002_closedeventschedule_and_more.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='ClosedEventSchedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('canonical_date', models.DateField()), - ], - options={ - 'abstract': False, - }, - ), - migrations.AlterUniqueTogether( - name='categoryitemassociation', - unique_together={('item', 'category')}, - ), - ] diff --git a/src/eateries/migrations/0003_closedeventschedule_eatery_and_more.py b/src/eateries/migrations/0003_closedeventschedule_eatery_and_more.py deleted file mode 100644 index b09825b..0000000 --- a/src/eateries/migrations/0003_closedeventschedule_eatery_and_more.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:19 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0002_closedeventschedule_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='closedeventschedule', - name='eatery', - field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore'), - preserve_default=False, - ), - migrations.AddField( - model_name='closedeventschedule', - name='event_description', - field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), - preserve_default=False, - ), - ] diff --git a/src/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py b/src/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py deleted file mode 100644 index de796ef..0000000 --- a/src/eateries/migrations/0004_dayofweekeventschedule_dateeventschedule.py +++ /dev/null @@ -1,40 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:23 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0003_closedeventschedule_eatery_and_more'), - ] - - operations = [ - migrations.CreateModel( - name='DayOfWeekEventSchedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('day_of_week', models.CharField(choices=[('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday')], max_length=10)), - ('start', models.TimeField()), - ('end', models.TimeField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='DateEventSchedule', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('canonical_date', models.DateField()), - ('start_timestamp', models.IntegerField()), - ('end_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/src/eateries/migrations/0005_dateeventschedule_event_description_and_more.py b/src/eateries/migrations/0005_dateeventschedule_event_description_and_more.py deleted file mode 100644 index e6a89a3..0000000 --- a/src/eateries/migrations/0005_dateeventschedule_event_description_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:25 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0004_dayofweekeventschedule_dateeventschedule'), - ] - - operations = [ - migrations.AddField( - model_name='dateeventschedule', - name='event_description', - field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), - preserve_default=False, - ), - migrations.AddField( - model_name='dateeventschedule', - name='menu', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore'), - preserve_default=False, - ), - migrations.AddField( - model_name='dayofweekeventschedule', - name='event_description', - field=models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], default='hello', max_length=10), - preserve_default=False, - ), - migrations.AddField( - model_name='dayofweekeventschedule', - name='menu', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore'), - preserve_default=False, - ), - ] diff --git a/src/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py b/src/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py deleted file mode 100644 index 0474536..0000000 --- a/src/eateries/migrations/0006_alter_categoryitemassociation_unique_together_and_more.py +++ /dev/null @@ -1,70 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 18:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0005_dateeventschedule_event_description_and_more'), - ] - - operations = [ - migrations.AlterUniqueTogether( - name='categoryitemassociation', - unique_together=set(), - ), - migrations.AlterField( - model_name='categoryitemassociation', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='categorystore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='closedeventschedule', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='dateeventschedule', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='dayofweekeventschedule', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='exceptionstore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='itemstore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='menustore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='subitemstore', - name='id', - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterUniqueTogether( - name='categorystore', - unique_together={('menu', 'category')}, - ), - migrations.AlterUniqueTogether( - name='menustore', - unique_together={('eatery', 'name')}, - ), - ] diff --git a/src/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py b/src/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py deleted file mode 100644 index e8d5d58..0000000 --- a/src/eateries/migrations/0007_rename_exceptionstore_alertstore_and_more.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.0 on 2022-01-11 13:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('eateries', '0006_alter_categoryitemassociation_unique_together_and_more'), - ] - - operations = [ - migrations.RenameModel( - old_name='ExceptionStore', - new_name='AlertStore', - ), - migrations.AlterUniqueTogether( - name='dayofweekeventschedule', - unique_together={('eatery', 'day_of_week', 'event_description')}, - ), - ] diff --git a/src/eateries/models.py b/src/eateries/models.py deleted file mode 100644 index 6045b29..0000000 --- a/src/eateries/models.py +++ /dev/null @@ -1,110 +0,0 @@ -from django.db import models -from datetime import datetime -class EateryStore(models.Model): - class CampusArea(models.TextChoices): - WEST = 'West' - NORTH = 'North' - CENTRAL = 'Central' - COLLEGETOWN = 'Collegetown' - NONE = '' - - id = models.IntegerField(primary_key=True) - name = models.CharField(max_length=40, blank=True) - menu_summary = models.CharField(max_length = 60, blank=True) - image_url = models.URLField(blank=True) - location = models.CharField(max_length=30, blank=True) - campus_area = models.CharField(max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True) - online_order_url = models.URLField(blank=True) - latitude = models.FloatField(null = True, blank=True) - longitude = models.FloatField(null = True, blank=True) - payment_accepts_meal_swipes = models.BooleanField(null = True, blank=True) - payment_accepts_brbs = models.BooleanField(null = True, blank=True) - payment_accepts_cash = models.BooleanField(null = True, blank=True) - -class AlertStore(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - description = models.CharField(max_length = 250) - start_timestamp = models.IntegerField() - end_timestamp = models.IntegerField() - -class MenuStore(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - name = models.CharField(max_length = 40) - - class Meta: - unique_together = ('eatery', 'name') - -class CategoryStore(models.Model): - id = models.IntegerField(primary_key=True) - menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - category = models.CharField(max_length = 40) - - class Meta: - unique_together = ('menu', 'category') - -class ItemStore(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - name = models.CharField(max_length=40) - description = models.CharField(max_length = 200, blank=True) - base_price = models.FloatField(null = True, blank=True) - -class SubItemStore(models.Model): - id = models.IntegerField(primary_key=True) - item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) - additional_price = models.FloatField(null = True, blank=True) - total_price = models.FloatField(null = True, blank=True) - name = models.CharField(max_length=40) - item_subsection = models.CharField(max_length=40) - -class CategoryItemAssociation(models.Model): - id = models.IntegerField(primary_key=True) - item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) - category = models.ForeignKey(CategoryStore, on_delete=models.DO_NOTHING) -class EventDescription(models.TextChoices): - BREAKFAST = 'Breakfast' - BRUNCH = 'Brunch' - LUNCH = 'Lunch' - DINNER = 'Dinner' - GENERAL = 'General' - -class EventSchedule(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - class Meta: - abstract=True - -class DayOfWeekEventSchedule(EventSchedule): - - class DayOfTheWeek(models.TextChoices): - MONDAY = 'Monday' - TUESDAY = 'Tuesday' - WEDNESDAY = 'Wednesday' - THURSDAY = 'Thursday' - FRIDAY = 'Friday' - SATURDAY = 'Saturday' - SUNDAY = 'Sunday' - - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) - menu= models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=10) - start = models.TimeField() - end = models.TimeField() - class Meta: - unique_together = ('eatery', 'day_of_week', 'event_description') - - -class DateEventSchedule(EventSchedule): - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) - menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - canonical_date = models.DateField() - start_timestamp = models.IntegerField() - end_timestamp = models.IntegerField() - -class ClosedEventSchedule(EventSchedule): - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) - canonical_date = models.DateField() - - diff --git a/src/eateries/models/AlertModel.py b/src/eateries/models/AlertModel.py new file mode 100644 index 0000000..d4932b1 --- /dev/null +++ b/src/eateries/models/AlertModel.py @@ -0,0 +1,9 @@ +from django.db import models +from eateries.models.EateryModel import EateryStore + +class AlertStore(models.Model): + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + description = models.CharField(max_length = 250) + start_timestamp = models.IntegerField() + end_timestamp = models.IntegerField() diff --git a/src/eateries/models/EateryModel.py b/src/eateries/models/EateryModel.py new file mode 100644 index 0000000..6598787 --- /dev/null +++ b/src/eateries/models/EateryModel.py @@ -0,0 +1,22 @@ +from django.db import models + +class EateryStore(models.Model): + class CampusArea(models.TextChoices): + WEST = 'West' + NORTH = 'North' + CENTRAL = 'Central' + COLLEGETOWN = 'Collegetown' + NONE = '' + + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=40, blank=True) + menu_summary = models.CharField(max_length = 60, blank=True) + image_url = models.URLField(blank=True) + location = models.CharField(max_length=30, blank=True) + campus_area = models.CharField(max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True) + online_order_url = models.URLField(blank=True) + latitude = models.FloatField(null = True, blank=True) + longitude = models.FloatField(null = True, blank=True) + payment_accepts_meal_swipes = models.BooleanField(null = True, blank=True) + payment_accepts_brbs = models.BooleanField(null = True, blank=True) + payment_accepts_cash = models.BooleanField(null = True, blank=True) diff --git a/src/eateries/models/EventScheduleModel.py b/src/eateries/models/EventScheduleModel.py new file mode 100644 index 0000000..3965609 --- /dev/null +++ b/src/eateries/models/EventScheduleModel.py @@ -0,0 +1,47 @@ +from django.db import models +from eateries.models.EateryModel import EateryStore +from eateries.models.MenuModel import MenuStore + +class EventDescription(models.TextChoices): + BREAKFAST = 'Breakfast' + BRUNCH = 'Brunch' + LUNCH = 'Lunch' + DINNER = 'Dinner' + GENERAL = 'General' + +class EventSchedule(models.Model): + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + class Meta: + abstract=True + +class DayOfWeekEventSchedule(EventSchedule): + + class DayOfTheWeek(models.TextChoices): + MONDAY = 'Monday' + TUESDAY = 'Tuesday' + WEDNESDAY = 'Wednesday' + THURSDAY = 'Thursday' + FRIDAY = 'Friday' + SATURDAY = 'Saturday' + SUNDAY = 'Sunday' + + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + menu= models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=10) + start = models.TimeField() + end = models.TimeField() + class Meta: + unique_together = ('eatery', 'day_of_week', 'event_description') + + +class DateEventSchedule(EventSchedule): + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + canonical_date = models.DateField() + start_timestamp = models.IntegerField() + end_timestamp = models.IntegerField() + +class ClosedEventSchedule(EventSchedule): + event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + canonical_date = models.DateField() diff --git a/src/eateries/models/MenuModel.py b/src/eateries/models/MenuModel.py new file mode 100644 index 0000000..10efaba --- /dev/null +++ b/src/eateries/models/MenuModel.py @@ -0,0 +1,38 @@ +from django.db import models +from eateries.models.EateryModel import EateryStore + +class MenuStore(models.Model): + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + name = models.CharField(max_length = 40) + + class Meta: + unique_together = ('eatery', 'name') + +class CategoryStore(models.Model): + id = models.IntegerField(primary_key=True) + menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + category = models.CharField(max_length = 40) + + class Meta: + unique_together = ('menu', 'category') + +class ItemStore(models.Model): + id = models.IntegerField(primary_key=True) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + name = models.CharField(max_length=40) + description = models.CharField(max_length = 200, blank=True) + base_price = models.FloatField(null = True, blank=True) + +class SubItemStore(models.Model): + id = models.IntegerField(primary_key=True) + item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) + additional_price = models.FloatField(null = True, blank=True) + total_price = models.FloatField(null = True, blank=True) + name = models.CharField(max_length=40) + item_subsection = models.CharField(max_length=40) + +class CategoryItemAssociation(models.Model): + id = models.IntegerField(primary_key=True) + item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) + category = models.ForeignKey(CategoryStore, on_delete=models.DO_NOTHING) diff --git a/src/eateries/models/ReportModel.py b/src/eateries/models/ReportModel.py new file mode 100644 index 0000000..3bb51d5 --- /dev/null +++ b/src/eateries/models/ReportModel.py @@ -0,0 +1,8 @@ +from django.db import models +from eateries.models.EateryModel import EateryStore + +class ReportStore(models.Model): + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + type = models.CharField(max_length=200) + content = models.TextField() + created_timestamp = models.IntegerField() \ No newline at end of file diff --git a/src/transactions/models.py b/src/eateries/models/TransactionModel.py similarity index 82% rename from src/transactions/models.py rename to src/eateries/models/TransactionModel.py index 53d660b..3b578b8 100644 --- a/src/transactions/models.py +++ b/src/eateries/models/TransactionModel.py @@ -1,9 +1,8 @@ from django.db import models -from eateries.models import EateryStore -# Create your models here. +from eateries.models.EateryModel import EateryStore # [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] -class TransactionHistory(models.Model): +class TransactionHistoryStore(models.Model): class Meta: unique_together = ('eatery_id', 'block_end_time', 'canonical_date') indexes = [models.Index(fields = ['canonical_date'])] diff --git a/src/eateries/models/__init__.py b/src/eateries/models/__init__.py new file mode 100644 index 0000000..420d31f --- /dev/null +++ b/src/eateries/models/__init__.py @@ -0,0 +1,9 @@ +# Need to expose models to django. +# In this app, models should be imported directly from eateries.models, not from eateries.models.package + +from .AlertModel import AlertStore +from .EateryModel import EateryStore +from .EventScheduleModel import EventSchedule, ClosedEventSchedule, DateEventSchedule, DayOfWeekEventSchedule +from .MenuModel import MenuStore, CategoryStore, ItemStore, SubItemStore, CategoryItemAssociation +from .ReportModel import ReportStore +from .TransactionModel import TransactionHistoryStore \ No newline at end of file diff --git a/src/eateries/views.py b/src/eateries/views.py index 91ea44a..2ddedd6 100644 --- a/src/eateries/views.py +++ b/src/eateries/views.py @@ -1,3 +1,21 @@ from django.shortcuts import render +from django.http import JsonResponse +from dfg.datatype.Eatery import EateryID +from eateries.controllers.create_report import CreateReportController +from rest_framework.views import APIView +from util.json import verify_json_fields, success_json, error_json, FieldType +import json # Create your views here. + +class ReportView(APIView): + def post(self, request): + json_body = json.loads(request.body) + if not verify_json_fields(json_body, {"eatery_id": FieldType.INT, "type": FieldType.STRING, "content": FieldType.STRING}): + return JsonResponse(error_json("Malformed Request")) + CreateReportController( + eatery_id=EateryID(json_body["eatery_id"]), + type=json_body["type"], + content=json_body["content"] + ).process() + return JsonResponse(success_json("Reported")) \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index f63a119..ffc709f 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -38,7 +38,6 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "transactions", "eateries", "rest_framework" ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 8db22b1..b2d83e7 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -16,7 +16,15 @@ from django.contrib import admin from django.urls import include, path +from dfg.views import MainDfgView +from eateries.views import ReportView + +api = [ + path("", MainDfgView.as_view(), name="main"), + path("report", ReportView.as_view(), name="report") +] + urlpatterns = [ - path("api", include("api.urls")), + path("api/", include(api)), path("admin/", admin.site.urls), ] diff --git a/src/transactions/__init__.py b/src/transactions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/transactions/admin.py b/src/transactions/admin.py deleted file mode 100644 index 784107d..0000000 --- a/src/transactions/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin - -from .models import TransactionHistory -# Register your models here. - -admin.site.register(TransactionHistory) \ No newline at end of file diff --git a/src/transactions/apps.py b/src/transactions/apps.py deleted file mode 100644 index f806531..0000000 --- a/src/transactions/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class TransactionsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'transactions' diff --git a/src/transactions/migrations/0001_initial.py b/src/transactions/migrations/0001_initial.py deleted file mode 100644 index 5178f47..0000000 --- a/src/transactions/migrations/0001_initial.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 4.0 on 2022-01-10 17:56 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('eateries', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='TransactionHistory', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('canonical_date', models.DateField()), - ('block_end_time', models.TimeField()), - ('transaction_count', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ], - ), - migrations.AddIndex( - model_name='transactionhistory', - index=models.Index(fields=['canonical_date'], name='transaction_canonic_a40422_idx'), - ), - migrations.AlterUniqueTogether( - name='transactionhistory', - unique_together={('eatery_id', 'block_end_time', 'canonical_date')}, - ), - ] diff --git a/src/transactions/migrations/__init__.py b/src/transactions/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/transactions/tests.py b/src/transactions/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/transactions/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/transactions/views.py b/src/transactions/views.py deleted file mode 100644 index b91e46a..0000000 --- a/src/transactions/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. \ No newline at end of file diff --git a/src/util/constants.py b/src/util/constants.py index b9108be..ea047d0 100644 --- a/src/util/constants.py +++ b/src/util/constants.py @@ -1,6 +1,6 @@ from enum import Enum -from api.datatype.Eatery import EateryID +from dfg.datatype.Eatery import EateryID CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" diff --git a/src/util/json.py b/src/util/json.py new file mode 100644 index 0000000..4c96aec --- /dev/null +++ b/src/util/json.py @@ -0,0 +1,41 @@ +from typing import Mapping +from enum import Enum + +from dfg.datatype.Eatery import EateryID + +class FieldType(Enum): + INT = 'int' + STRING = 'str' + EATERYID = "eateryid" + + +def verify_json_fields(json, field_type_map: Mapping[str, FieldType]): + for field in field_type_map: + print(field) + if field not in json: + return False + if field_type_map[field] is FieldType.INT: + if not isinstance(json[field], int): + return False + elif field_type_map[field] is FieldType.STRING: + if not isinstance(json[field], str): + return False + elif field_type_map[field] is FieldType.EATERYID: + if not isinstance(json[field], int) or EateryID(json[field]) == None: + return False + + return True + +def success_json(data): + return { + "success": True, + "data": data, + "error": None + } + +def error_json(error: str): + return { + "success": False, + "data": None, + "error": error + } From 5dc8e20f368111e20bc6b840b1cfb1b33dd69090 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 13 Jan 2022 12:29:28 -0500 Subject: [PATCH 051/305] More restructuring --- src/{dfg => api}/__init__.py | 0 src/{eateries => api}/admin.py | 2 +- src/{eateries => api}/apps.py | 4 +- .../controllers/create_report.py | 4 +- .../controllers/create_transaction.py | 5 +- .../controllers/delete_all_transactions.py | 2 +- src/{dfg => api}/datatype/Eatery.py | 6 +- src/{dfg => api}/datatype/EateryAlert.py | 0 src/{dfg => api}/datatype/Event.py | 4 +- src/{dfg => api}/datatype/Menu.py | 2 +- src/{dfg => api}/datatype/MenuCategory.py | 2 +- src/{dfg => api}/datatype/MenuItem.py | 2 +- src/{dfg => api}/datatype/MenuItemSection.py | 2 +- src/{dfg => api}/datatype/MenuSubItem.py | 0 src/{dfg => api}/datatype/WaitTime.py | 0 src/{dfg => api}/datatype/WaitTimesDay.py | 0 src/api/dfg/main.py | 50 ++++++++++++++ src/{ => api}/dfg/nodes/CornellDiningNow.py | 9 +-- src/{ => api}/dfg/nodes/DfgNode.py | 0 src/{ => api}/dfg/nodes/EateriesFromDB.py | 22 ++---- src/{ => api}/dfg/nodes/EateryStubs.py | 4 +- src/{ => api}/dfg/nodes/__init__.py | 0 .../dfg/nodes/macros/EateryEvents.py | 16 ++--- .../dfg/nodes/macros/LeftMergeEateries.py | 8 +-- .../dfg/nodes/macros/LeftMergeEvents.py | 8 +-- .../dfg/nodes/schedule/CacheMenuInjection.py | 18 ++--- .../dfg/nodes/schedule/ClosedSchedule.py | 4 +- .../dfg/nodes/schedule/CornellDiningEvents.py | 14 ++-- .../dfg/nodes/schedule/DateSchedule.py | 4 +- .../dfg/nodes/schedule/DayOfWeekSchedule.py | 14 ++-- .../dfg/nodes/system/ConvertFromJson.py | 6 +- .../dfg/nodes/system/ConvertToJson.py | 6 +- .../dfg/nodes/system/DictResponseWrapper.py | 5 +- .../dfg/nodes/system/EateryGenerator.py | 4 +- .../dfg/nodes/system/InMemoryCache.py | 4 +- src/{ => api}/dfg/nodes/system/LeftMerge.py | 2 +- src/{ => api}/dfg/nodes/system/Mapping.py | 4 +- .../dfg/nodes/wait_times/WaitTimeFilter.py | 4 +- .../dfg/nodes/wait_times/WaitTimes.py | 18 +++-- .../management/commands/export_db_snapshot.py | 10 +-- .../management/commands/ingest_db_snapshot.py | 5 +- .../commands/ingest_log_transactions.py | 9 ++- .../commands/ingest_recent_transactions.py | 2 +- .../migrations/0001_initial.py | 32 ++++----- src/{eateries => api/migrations}/__init__.py | 0 src/{eateries => api}/models/AlertModel.py | 2 +- src/{eateries => api}/models/EateryModel.py | 0 .../models/EventScheduleModel.py | 4 +- src/{eateries => api}/models/MenuModel.py | 2 +- src/{eateries => api}/models/ReportModel.py | 2 +- .../models/TransactionModel.py | 2 +- src/{eateries => api}/models/__init__.py | 2 +- src/{eateries => api}/serializers.py | 2 +- src/api/urls.py | 8 +++ src/{ => api}/util/constants.py | 2 +- src/{ => api}/util/json.py | 2 +- src/{ => api}/util/time.py | 0 src/api/views.py | 36 ++++++++++ src/dfg/admin.py | 3 - src/dfg/apps.py | 6 -- src/dfg/tests.py | 3 - src/dfg/views.py | 68 ------------------- src/eateries/migrations/__init__.py | 0 src/eateries/tests.py | 3 - src/eateries/views.py | 21 ------ src/eatery_blue_backend/settings.py | 2 +- src/eatery_blue_backend/urls.py | 10 +-- 67 files changed, 230 insertions(+), 267 deletions(-) rename src/{dfg => api}/__init__.py (100%) rename src/{eateries => api}/admin.py (94%) rename src/{eateries => api}/apps.py (62%) rename src/{eateries => api}/controllers/create_report.py (86%) rename src/{eateries => api}/controllers/create_transaction.py (93%) rename src/{eateries => api}/controllers/delete_all_transactions.py (72%) rename src/{dfg => api}/datatype/Eatery.py (97%) rename src/{dfg => api}/datatype/EateryAlert.py (100%) rename src/{dfg => api}/datatype/Event.py (96%) rename src/{dfg => api}/datatype/Menu.py (87%) rename src/{dfg => api}/datatype/MenuCategory.py (92%) rename src/{dfg => api}/datatype/MenuItem.py (96%) rename src/{dfg => api}/datatype/MenuItemSection.py (91%) rename src/{dfg => api}/datatype/MenuSubItem.py (100%) rename src/{dfg => api}/datatype/WaitTime.py (100%) rename src/{dfg => api}/datatype/WaitTimesDay.py (100%) create mode 100644 src/api/dfg/main.py rename src/{ => api}/dfg/nodes/CornellDiningNow.py (89%) rename src/{ => api}/dfg/nodes/DfgNode.py (100%) rename src/{ => api}/dfg/nodes/EateriesFromDB.py (84%) rename src/{ => api}/dfg/nodes/EateryStubs.py (68%) rename src/{ => api}/dfg/nodes/__init__.py (100%) rename src/{ => api}/dfg/nodes/macros/EateryEvents.py (63%) rename src/{ => api}/dfg/nodes/macros/LeftMergeEateries.py (76%) rename src/{ => api}/dfg/nodes/macros/LeftMergeEvents.py (81%) rename src/{ => api}/dfg/nodes/schedule/CacheMenuInjection.py (89%) rename src/{ => api}/dfg/nodes/schedule/ClosedSchedule.py (83%) rename src/{ => api}/dfg/nodes/schedule/CornellDiningEvents.py (92%) rename src/{ => api}/dfg/nodes/schedule/DateSchedule.py (80%) rename src/{ => api}/dfg/nodes/schedule/DayOfWeekSchedule.py (82%) rename src/{ => api}/dfg/nodes/system/ConvertFromJson.py (91%) rename src/{ => api}/dfg/nodes/system/ConvertToJson.py (87%) rename src/{ => api}/dfg/nodes/system/DictResponseWrapper.py (84%) rename src/{ => api}/dfg/nodes/system/EateryGenerator.py (88%) rename src/{ => api}/dfg/nodes/system/InMemoryCache.py (96%) rename src/{ => api}/dfg/nodes/system/LeftMerge.py (98%) rename src/{ => api}/dfg/nodes/system/Mapping.py (84%) rename src/{ => api}/dfg/nodes/wait_times/WaitTimeFilter.py (93%) rename src/{ => api}/dfg/nodes/wait_times/WaitTimes.py (94%) rename src/{eateries => api}/management/commands/export_db_snapshot.py (96%) rename src/{eateries => api}/management/commands/ingest_db_snapshot.py (94%) rename src/{eateries => api}/management/commands/ingest_log_transactions.py (81%) rename src/{eateries => api}/management/commands/ingest_recent_transactions.py (93%) rename src/{eateries => api}/migrations/0001_initial.py (89%) rename src/{eateries => api/migrations}/__init__.py (100%) rename src/{eateries => api}/models/AlertModel.py (86%) rename src/{eateries => api}/models/EateryModel.py (100%) rename src/{eateries => api}/models/EventScheduleModel.py (94%) rename src/{eateries => api}/models/MenuModel.py (96%) rename src/{eateries => api}/models/ReportModel.py (83%) rename src/{eateries => api}/models/TransactionModel.py (91%) rename src/{eateries => api}/models/__init__.py (79%) rename src/{eateries => api}/serializers.py (97%) create mode 100644 src/api/urls.py rename src/{ => api}/util/constants.py (99%) rename src/{ => api}/util/json.py (96%) rename src/{ => api}/util/time.py (100%) create mode 100644 src/api/views.py delete mode 100644 src/dfg/admin.py delete mode 100644 src/dfg/apps.py delete mode 100644 src/dfg/tests.py delete mode 100644 src/dfg/views.py delete mode 100644 src/eateries/migrations/__init__.py delete mode 100644 src/eateries/tests.py delete mode 100644 src/eateries/views.py diff --git a/src/dfg/__init__.py b/src/api/__init__.py similarity index 100% rename from src/dfg/__init__.py rename to src/api/__init__.py diff --git a/src/eateries/admin.py b/src/api/admin.py similarity index 94% rename from src/eateries/admin.py rename to src/api/admin.py index 3f75c02..70c50e0 100644 --- a/src/eateries/admin.py +++ b/src/api/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -import eateries.models as models +import api.models as models # Register your models here. admin.site.register(models.EateryStore) diff --git a/src/eateries/apps.py b/src/api/apps.py similarity index 62% rename from src/eateries/apps.py rename to src/api/apps.py index 6a490f8..66656fd 100644 --- a/src/eateries/apps.py +++ b/src/api/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class EateriesConfig(AppConfig): +class ApiConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'eateries' + name = 'api' diff --git a/src/eateries/controllers/create_report.py b/src/api/controllers/create_report.py similarity index 86% rename from src/eateries/controllers/create_report.py rename to src/api/controllers/create_report.py index 3ccd4af..b0e026f 100644 --- a/src/eateries/controllers/create_report.py +++ b/src/api/controllers/create_report.py @@ -1,6 +1,6 @@ from datetime import datetime -from dfg.datatype.Eatery import EateryID -from eateries.models import ReportStore +from api.datatype.Eatery import EateryID +from api.models import ReportStore class CreateReportController: diff --git a/src/eateries/controllers/create_transaction.py b/src/api/controllers/create_transaction.py similarity index 93% rename from src/eateries/controllers/create_transaction.py rename to src/api/controllers/create_transaction.py index 2e41e1a..db70b6f 100644 --- a/src/eateries/controllers/create_transaction.py +++ b/src/api/controllers/create_transaction.py @@ -1,8 +1,9 @@ from datetime import datetime, timedelta -from eateries.models import TransactionHistoryStore -from util.constants import vendor_name_to_internal_id import pytz +from api.models import TransactionHistoryStore +from api.util.constants import vendor_name_to_internal_id + class CreateTransactionController: def __init__(self, data): diff --git a/src/eateries/controllers/delete_all_transactions.py b/src/api/controllers/delete_all_transactions.py similarity index 72% rename from src/eateries/controllers/delete_all_transactions.py rename to src/api/controllers/delete_all_transactions.py index a0cf85d..58b6035 100644 --- a/src/eateries/controllers/delete_all_transactions.py +++ b/src/api/controllers/delete_all_transactions.py @@ -1,5 +1,5 @@ -from eateries.models import TransactionHistoryStore +from api.models import TransactionHistoryStore class DeleteAllTransactionsController: diff --git a/src/dfg/datatype/Eatery.py b/src/api/datatype/Eatery.py similarity index 97% rename from src/dfg/datatype/Eatery.py rename to src/api/datatype/Eatery.py index 5673746..349dc15 100644 --- a/src/dfg/datatype/Eatery.py +++ b/src/api/datatype/Eatery.py @@ -3,10 +3,10 @@ from enum import Enum import pytz -from dfg.datatype.EateryAlert import EateryAlert +from api.datatype.EateryAlert import EateryAlert -from dfg.datatype.Event import Event, filter_range -from dfg.datatype.WaitTimesDay import WaitTimesDay +from api.datatype.Event import Event, filter_range +from api.datatype.WaitTimesDay import WaitTimesDay class EateryID(Enum): diff --git a/src/dfg/datatype/EateryAlert.py b/src/api/datatype/EateryAlert.py similarity index 100% rename from src/dfg/datatype/EateryAlert.py rename to src/api/datatype/EateryAlert.py diff --git a/src/dfg/datatype/Event.py b/src/api/datatype/Event.py similarity index 96% rename from src/dfg/datatype/Event.py rename to src/api/datatype/Event.py index f0877b4..d3a9175 100644 --- a/src/dfg/datatype/Event.py +++ b/src/api/datatype/Event.py @@ -1,7 +1,7 @@ from typing import Optional from datetime import date, time -from dfg.datatype.Menu import Menu -from util.time import combined_timestamp +from api.datatype.Menu import Menu +from api.util.time import combined_timestamp import pytz diff --git a/src/dfg/datatype/Menu.py b/src/api/datatype/Menu.py similarity index 87% rename from src/dfg/datatype/Menu.py rename to src/api/datatype/Menu.py index 7fead2b..c48e100 100644 --- a/src/dfg/datatype/Menu.py +++ b/src/api/datatype/Menu.py @@ -1,4 +1,4 @@ -from dfg.datatype.MenuCategory import MenuCategory +from api.datatype.MenuCategory import MenuCategory class Menu: diff --git a/src/dfg/datatype/MenuCategory.py b/src/api/datatype/MenuCategory.py similarity index 92% rename from src/dfg/datatype/MenuCategory.py rename to src/api/datatype/MenuCategory.py index 0b34049..f2cd1cc 100644 --- a/src/dfg/datatype/MenuCategory.py +++ b/src/api/datatype/MenuCategory.py @@ -1,4 +1,4 @@ -from dfg.datatype.MenuItem import MenuItem +from api.datatype.MenuItem import MenuItem class MenuCategory: diff --git a/src/dfg/datatype/MenuItem.py b/src/api/datatype/MenuItem.py similarity index 96% rename from src/dfg/datatype/MenuItem.py rename to src/api/datatype/MenuItem.py index 5fd48b3..b611652 100644 --- a/src/dfg/datatype/MenuItem.py +++ b/src/api/datatype/MenuItem.py @@ -1,6 +1,6 @@ from typing import Optional -from dfg.datatype.MenuItemSection import MenuItemSection +from api.datatype.MenuItemSection import MenuItemSection class MenuItem: diff --git a/src/dfg/datatype/MenuItemSection.py b/src/api/datatype/MenuItemSection.py similarity index 91% rename from src/dfg/datatype/MenuItemSection.py rename to src/api/datatype/MenuItemSection.py index ba415f3..770c376 100644 --- a/src/dfg/datatype/MenuItemSection.py +++ b/src/api/datatype/MenuItemSection.py @@ -1,4 +1,4 @@ -from dfg.datatype.MenuSubItem import MenuSubItem +from api.datatype.MenuSubItem import MenuSubItem class MenuItemSection: diff --git a/src/dfg/datatype/MenuSubItem.py b/src/api/datatype/MenuSubItem.py similarity index 100% rename from src/dfg/datatype/MenuSubItem.py rename to src/api/datatype/MenuSubItem.py diff --git a/src/dfg/datatype/WaitTime.py b/src/api/datatype/WaitTime.py similarity index 100% rename from src/dfg/datatype/WaitTime.py rename to src/api/datatype/WaitTime.py diff --git a/src/dfg/datatype/WaitTimesDay.py b/src/api/datatype/WaitTimesDay.py similarity index 100% rename from src/dfg/datatype/WaitTimesDay.py rename to src/api/datatype/WaitTimesDay.py diff --git a/src/api/dfg/main.py b/src/api/dfg/main.py new file mode 100644 index 0000000..8de2b9f --- /dev/null +++ b/src/api/dfg/main.py @@ -0,0 +1,50 @@ +from api.dfg.nodes.CornellDiningNow import CornellDiningNow +from api.dfg.nodes.EateryStubs import EateryStubs +from api.dfg.nodes.EateriesFromDB import EateriesFromDB +from api.dfg.nodes.macros.EateryEvents import EateryEvents + +from api.dfg.nodes.system.DictResponseWrapper import DictResponseWrapper +from api.dfg.nodes.system.ConvertToJson import ConvertToJson +from api.dfg.nodes.system.EateryGenerator import EateryGenerator +from api.dfg.nodes.system.InMemoryCache import InMemoryCache +from api.dfg.nodes.system.Mapping import Mapping + +from api.dfg.nodes.macros.LeftMergeEateries import LeftMergeEateries + +from api.dfg.nodes.wait_times.WaitTimes import WaitTimes +from api.dfg.nodes.wait_times.WaitTimeFilter import WaitTimeFilter + +main_dfg = DictResponseWrapper( + ConvertToJson( + InMemoryCache( + WaitTimeFilter( + LeftMergeEateries( + Mapping( + child=EateryStubs(), + fn = lambda eatery, cache: EateryGenerator( + eatery_id=eatery.id, + wait_times_dfg=WaitTimes(eatery.id, cache) + ) + ), + LeftMergeEateries( + Mapping( + child = EateryStubs(), + fn = lambda eatery, cache: EateryGenerator( + eatery_id=eatery.id, + events_dfg=EateryEvents(eatery.id, cache) + ) + ), + LeftMergeEateries( + EateriesFromDB(), + LeftMergeEateries( + CornellDiningNow(), + EateryStubs() + ) + ) + ) + ) + ) + ) + ), + re_raise_exceptions=True +) \ No newline at end of file diff --git a/src/dfg/nodes/CornellDiningNow.py b/src/api/dfg/nodes/CornellDiningNow.py similarity index 89% rename from src/dfg/nodes/CornellDiningNow.py rename to src/api/dfg/nodes/CornellDiningNow.py index 7ae89c4..7f1e067 100644 --- a/src/dfg/nodes/CornellDiningNow.py +++ b/src/api/dfg/nodes/CornellDiningNow.py @@ -1,11 +1,8 @@ import requests -from dfg.nodes.DfgNode import DfgNode - -from dfg.datatype.Eatery import Eatery -from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL - -from datetime import date +from api.dfg.nodes.DfgNode import DfgNode +from api.datatype.Eatery import Eatery +from api.util.constants import dining_id_to_internal_id, CORNELL_DINING_URL class CornellDiningNow(DfgNode): diff --git a/src/dfg/nodes/DfgNode.py b/src/api/dfg/nodes/DfgNode.py similarity index 100% rename from src/dfg/nodes/DfgNode.py rename to src/api/dfg/nodes/DfgNode.py diff --git a/src/dfg/nodes/EateriesFromDB.py b/src/api/dfg/nodes/EateriesFromDB.py similarity index 84% rename from src/dfg/nodes/EateriesFromDB.py rename to src/api/dfg/nodes/EateriesFromDB.py index d149083..6bcf9ee 100644 --- a/src/dfg/nodes/EateriesFromDB.py +++ b/src/api/dfg/nodes/EateriesFromDB.py @@ -1,21 +1,11 @@ -import datetime -import json -import re - -import pytz - -from dfg.datatype.Eatery import Eatery, EateryID -from dfg.datatype.EateryAlert import EateryAlert -from dfg.datatype.Event import Event -from dfg.datatype.Menu import Menu -from dfg.datatype.MenuCategory import MenuCategory -from dfg.datatype.MenuItem import MenuItem -from dfg.nodes.DfgNode import DfgNode +from datetime import datetime -from eateries.models import EateryStore, AlertStore -from eateries.serializers import EateryStoreSerializer, AlertStoreSerializer +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.EateryAlert import EateryAlert +from api.dfg.nodes.DfgNode import DfgNode +from api.models import EateryStore, AlertStore +from api.serializers import EateryStoreSerializer, AlertStoreSerializer -from datetime import datetime # eventually need to deprecate this for a custom DB backend storing all of the overrides diff --git a/src/dfg/nodes/EateryStubs.py b/src/api/dfg/nodes/EateryStubs.py similarity index 68% rename from src/dfg/nodes/EateryStubs.py rename to src/api/dfg/nodes/EateryStubs.py index 04b0561..59a9a08 100644 --- a/src/dfg/nodes/EateryStubs.py +++ b/src/api/dfg/nodes/EateryStubs.py @@ -1,5 +1,5 @@ -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery, EateryID +from api.dfg.nodes.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID class EateryStubs(DfgNode): diff --git a/src/dfg/nodes/__init__.py b/src/api/dfg/nodes/__init__.py similarity index 100% rename from src/dfg/nodes/__init__.py rename to src/api/dfg/nodes/__init__.py diff --git a/src/dfg/nodes/macros/EateryEvents.py b/src/api/dfg/nodes/macros/EateryEvents.py similarity index 63% rename from src/dfg/nodes/macros/EateryEvents.py rename to src/api/dfg/nodes/macros/EateryEvents.py index babdc76..ebfa9d1 100644 --- a/src/dfg/nodes/macros/EateryEvents.py +++ b/src/api/dfg/nodes/macros/EateryEvents.py @@ -1,14 +1,14 @@ -from dfg.nodes.DfgNode import DfgNode +from api.dfg.nodes.DfgNode import DfgNode -from dfg.nodes.schedule.ClosedSchedule import ClosedSchedule -from dfg.nodes.schedule.DayOfWeekSchedule import DayOfWeekSchedule -from dfg.nodes.schedule.DateSchedule import DateSchedule -from dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents +from api.dfg.nodes.schedule.ClosedSchedule import ClosedSchedule +from api.dfg.nodes.schedule.DayOfWeekSchedule import DayOfWeekSchedule +from api.dfg.nodes.schedule.DateSchedule import DateSchedule +from api.dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents -from dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents +from api.dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents -from dfg.datatype.Eatery import EateryID -from dfg.nodes.schedule.CacheMenuInjection import CacheMenuInjection +from api.datatype.Eatery import EateryID +from api.dfg.nodes.schedule.CacheMenuInjection import CacheMenuInjection # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if # conflict) diff --git a/src/dfg/nodes/macros/LeftMergeEateries.py b/src/api/dfg/nodes/macros/LeftMergeEateries.py similarity index 76% rename from src/dfg/nodes/macros/LeftMergeEateries.py rename to src/api/dfg/nodes/macros/LeftMergeEateries.py index 9eb0bf6..ab6b544 100644 --- a/src/dfg/nodes/macros/LeftMergeEateries.py +++ b/src/api/dfg/nodes/macros/LeftMergeEateries.py @@ -1,7 +1,7 @@ -from dfg.nodes.DfgNode import DfgNode -from dfg.nodes.system.ConvertToJson import ConvertToJson -from dfg.nodes.system.ConvertFromJson import EateryFromJson -from dfg.nodes.system.LeftMerge import LeftMerge +from api.dfg.nodes.DfgNode import DfgNode +from api.dfg.nodes.system.ConvertToJson import ConvertToJson +from api.dfg.nodes.system.ConvertFromJson import EateryFromJson +from api.dfg.nodes.system.LeftMerge import LeftMerge class LeftMergeEateries(DfgNode): diff --git a/src/dfg/nodes/macros/LeftMergeEvents.py b/src/api/dfg/nodes/macros/LeftMergeEvents.py similarity index 81% rename from src/dfg/nodes/macros/LeftMergeEvents.py rename to src/api/dfg/nodes/macros/LeftMergeEvents.py index 26bfc8f..db43fd3 100644 --- a/src/dfg/nodes/macros/LeftMergeEvents.py +++ b/src/api/dfg/nodes/macros/LeftMergeEvents.py @@ -1,8 +1,8 @@ -from dfg.nodes.DfgNode import DfgNode +from api.dfg.nodes.DfgNode import DfgNode -from dfg.nodes.system.ConvertToJson import ConvertToJson -from dfg.nodes.system.ConvertFromJson import EventFromJson -from dfg.nodes.system.LeftMerge import LeftMerge +from api.dfg.nodes.system.ConvertToJson import ConvertToJson +from api.dfg.nodes.system.ConvertFromJson import EventFromJson +from api.dfg.nodes.system.LeftMerge import LeftMerge # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if # conflict) diff --git a/src/dfg/nodes/schedule/CacheMenuInjection.py b/src/api/dfg/nodes/schedule/CacheMenuInjection.py similarity index 89% rename from src/dfg/nodes/schedule/CacheMenuInjection.py rename to src/api/dfg/nodes/schedule/CacheMenuInjection.py index cdab22c..3ea7601 100644 --- a/src/dfg/nodes/schedule/CacheMenuInjection.py +++ b/src/api/dfg/nodes/schedule/CacheMenuInjection.py @@ -1,11 +1,11 @@ -from dfg.datatype.Menu import Menu -from dfg.datatype.MenuCategory import MenuCategory -from dfg.datatype.MenuItem import MenuItem -from dfg.datatype.MenuItemSection import MenuItemSection -from dfg.datatype.MenuSubItem import MenuSubItem -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery, EateryID -from eateries.models import CategoryItemAssociation, SubItemStore +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem +from api.datatype.MenuItemSection import MenuItemSection +from api.datatype.MenuSubItem import MenuSubItem +from api.datatype.Eatery import Eatery, EateryID +from api.dfg.nodes.DfgNode import DfgNode +from api.models import CategoryItemAssociation, SubItemStore class CacheMenuInjection(DfgNode): @@ -84,4 +84,4 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: return self.child(*args, **kwargs) def description(self): - return "EateryStubs" + return "CacheMenuInjection" diff --git a/src/dfg/nodes/schedule/ClosedSchedule.py b/src/api/dfg/nodes/schedule/ClosedSchedule.py similarity index 83% rename from src/dfg/nodes/schedule/ClosedSchedule.py rename to src/api/dfg/nodes/schedule/ClosedSchedule.py index 766456f..52ca7d0 100644 --- a/src/dfg/nodes/schedule/ClosedSchedule.py +++ b/src/api/dfg/nodes/schedule/ClosedSchedule.py @@ -1,5 +1,5 @@ -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery, EateryID +from api.dfg.nodes.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID class ClosedSchedule(DfgNode): diff --git a/src/dfg/nodes/schedule/CornellDiningEvents.py b/src/api/dfg/nodes/schedule/CornellDiningEvents.py similarity index 92% rename from src/dfg/nodes/schedule/CornellDiningEvents.py rename to src/api/dfg/nodes/schedule/CornellDiningEvents.py index ec3d28d..f0b63a4 100644 --- a/src/dfg/nodes/schedule/CornellDiningEvents.py +++ b/src/api/dfg/nodes/schedule/CornellDiningEvents.py @@ -1,10 +1,10 @@ -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery, EateryID -from dfg.datatype.Event import Event -from dfg.datatype.Menu import Menu -from dfg.datatype.MenuCategory import MenuCategory -from dfg.datatype.MenuItem import MenuItem -from util.constants import dining_id_to_internal_id, CORNELL_DINING_URL +from api.dfg.nodes.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem +from api.util.constants import dining_id_to_internal_id, CORNELL_DINING_URL from datetime import date import requests diff --git a/src/dfg/nodes/schedule/DateSchedule.py b/src/api/dfg/nodes/schedule/DateSchedule.py similarity index 80% rename from src/dfg/nodes/schedule/DateSchedule.py rename to src/api/dfg/nodes/schedule/DateSchedule.py index a933d70..84ce774 100644 --- a/src/dfg/nodes/schedule/DateSchedule.py +++ b/src/api/dfg/nodes/schedule/DateSchedule.py @@ -1,5 +1,5 @@ -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery, EateryID +from api.dfg.nodes.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID # from eateries.models import DateEventSchedule class DateSchedule(DfgNode): diff --git a/src/dfg/nodes/schedule/DayOfWeekSchedule.py b/src/api/dfg/nodes/schedule/DayOfWeekSchedule.py similarity index 82% rename from src/dfg/nodes/schedule/DayOfWeekSchedule.py rename to src/api/dfg/nodes/schedule/DayOfWeekSchedule.py index 9cd586b..a469988 100644 --- a/src/dfg/nodes/schedule/DayOfWeekSchedule.py +++ b/src/api/dfg/nodes/schedule/DayOfWeekSchedule.py @@ -1,12 +1,10 @@ +from datetime import timedelta -from dfg.datatype.Event import Event -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery, EateryID -from eateries.models import DayOfWeekEventSchedule -from datetime import timedelta, datetime -from util.time import combined_timestamp - -import pytz +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.Event import Event +from api.dfg.nodes.DfgNode import DfgNode +from api.models import DayOfWeekEventSchedule +from api.util.time import combined_timestamp class DayOfWeekSchedule(DfgNode): diff --git a/src/dfg/nodes/system/ConvertFromJson.py b/src/api/dfg/nodes/system/ConvertFromJson.py similarity index 91% rename from src/dfg/nodes/system/ConvertFromJson.py rename to src/api/dfg/nodes/system/ConvertFromJson.py index a610e99..bcf8067 100644 --- a/src/dfg/nodes/system/ConvertFromJson.py +++ b/src/api/dfg/nodes/system/ConvertFromJson.py @@ -1,8 +1,8 @@ from typing import Union -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery -from dfg.datatype.Event import Event +from api.dfg.nodes.DfgNode import DfgNode +from api.datatype.Eatery import Eatery +from api.datatype.Event import Event class EateryFromJson(DfgNode): diff --git a/src/dfg/nodes/system/ConvertToJson.py b/src/api/dfg/nodes/system/ConvertToJson.py similarity index 87% rename from src/dfg/nodes/system/ConvertToJson.py rename to src/api/dfg/nodes/system/ConvertToJson.py index dbc5eab..c364035 100644 --- a/src/dfg/nodes/system/ConvertToJson.py +++ b/src/api/dfg/nodes/system/ConvertToJson.py @@ -1,8 +1,8 @@ from typing import Union -from dfg.datatype.Event import Event +from api.datatype.Event import Event -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery +from api.dfg.nodes.DfgNode import DfgNode +from api.datatype.Eatery import Eatery class ConvertToJson(DfgNode): diff --git a/src/dfg/nodes/system/DictResponseWrapper.py b/src/api/dfg/nodes/system/DictResponseWrapper.py similarity index 84% rename from src/dfg/nodes/system/DictResponseWrapper.py rename to src/api/dfg/nodes/system/DictResponseWrapper.py index 7001388..db3b67d 100644 --- a/src/dfg/nodes/system/DictResponseWrapper.py +++ b/src/api/dfg/nodes/system/DictResponseWrapper.py @@ -1,6 +1,5 @@ -from dfg.nodes.DfgNode import DfgNode -from util.json import success_json, error_json - +from api.dfg.nodes.DfgNode import DfgNode +from api.util.json import success_json, error_json class DictResponseWrapper(DfgNode): diff --git a/src/dfg/nodes/system/EateryGenerator.py b/src/api/dfg/nodes/system/EateryGenerator.py similarity index 88% rename from src/dfg/nodes/system/EateryGenerator.py rename to src/api/dfg/nodes/system/EateryGenerator.py index 53e0df6..e1b3d7a 100644 --- a/src/dfg/nodes/system/EateryGenerator.py +++ b/src/api/dfg/nodes/system/EateryGenerator.py @@ -1,5 +1,5 @@ -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery, EateryID +from api.dfg.nodes.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID from typing import Optional class EateryGenerator(DfgNode): diff --git a/src/dfg/nodes/system/InMemoryCache.py b/src/api/dfg/nodes/system/InMemoryCache.py similarity index 96% rename from src/dfg/nodes/system/InMemoryCache.py rename to src/api/dfg/nodes/system/InMemoryCache.py index f204a6b..9966a6c 100644 --- a/src/dfg/nodes/system/InMemoryCache.py +++ b/src/api/dfg/nodes/system/InMemoryCache.py @@ -1,9 +1,9 @@ import time -from dfg.nodes.DfgNode import DfgNode +from api.dfg.nodes.DfgNode import DfgNode from typing import Optional -from dfg.nodes.system.ConvertToJson import ConvertToJson +from api.dfg.nodes.system.ConvertToJson import ConvertToJson class DataSnapshot: diff --git a/src/dfg/nodes/system/LeftMerge.py b/src/api/dfg/nodes/system/LeftMerge.py similarity index 98% rename from src/dfg/nodes/system/LeftMerge.py rename to src/api/dfg/nodes/system/LeftMerge.py index 6b5170c..3f1edbb 100644 --- a/src/dfg/nodes/system/LeftMerge.py +++ b/src/api/dfg/nodes/system/LeftMerge.py @@ -1,4 +1,4 @@ -from dfg.nodes.DfgNode import DfgNode +from api.dfg.nodes.DfgNode import DfgNode from typing import Callable, TypeVar, Any from functools import cmp_to_key T = TypeVar("T") diff --git a/src/dfg/nodes/system/Mapping.py b/src/api/dfg/nodes/system/Mapping.py similarity index 84% rename from src/dfg/nodes/system/Mapping.py rename to src/api/dfg/nodes/system/Mapping.py index bd7aea2..565839c 100644 --- a/src/dfg/nodes/system/Mapping.py +++ b/src/api/dfg/nodes/system/Mapping.py @@ -1,5 +1,5 @@ -from dfg.nodes.DfgNode import DfgNode -from dfg.datatype.Eatery import Eatery, EateryID +from api.dfg.nodes.DfgNode import DfgNode +from api.datatype.Eatery import Eatery, EateryID from typing import Callable, Any class Mapping(DfgNode): diff --git a/src/dfg/nodes/wait_times/WaitTimeFilter.py b/src/api/dfg/nodes/wait_times/WaitTimeFilter.py similarity index 93% rename from src/dfg/nodes/wait_times/WaitTimeFilter.py rename to src/api/dfg/nodes/wait_times/WaitTimeFilter.py index 66ff36a..a2830fb 100644 --- a/src/dfg/nodes/wait_times/WaitTimeFilter.py +++ b/src/api/dfg/nodes/wait_times/WaitTimeFilter.py @@ -1,5 +1,5 @@ -from dfg.datatype.WaitTimesDay import WaitTimesDay -from dfg.nodes.DfgNode import DfgNode +from api.datatype.WaitTimesDay import WaitTimesDay +from api.dfg.nodes.DfgNode import DfgNode # Removes all wait times that are not part of the eatery's events diff --git a/src/dfg/nodes/wait_times/WaitTimes.py b/src/api/dfg/nodes/wait_times/WaitTimes.py similarity index 94% rename from src/dfg/nodes/wait_times/WaitTimes.py rename to src/api/dfg/nodes/wait_times/WaitTimes.py index f8a834a..a2470da 100644 --- a/src/dfg/nodes/wait_times/WaitTimes.py +++ b/src/api/dfg/nodes/wait_times/WaitTimes.py @@ -1,16 +1,14 @@ from datetime import date, timedelta - -import pytz from django.db.models import Avg +import pytz -from dfg.datatype.Eatery import Eatery, EateryID -from dfg.datatype.Event import Event -from dfg.datatype.WaitTime import WaitTime -from dfg.datatype.WaitTimesDay import WaitTimesDay -from dfg.nodes.DfgNode import DfgNode -from eateries.models import TransactionHistoryStore - -from util.time import combined_timestamp +from api.datatype.Eatery import Eatery, EateryID +from api.datatype.Event import Event +from api.datatype.WaitTime import WaitTime +from api.datatype.WaitTimesDay import WaitTimesDay +from api.dfg.nodes.DfgNode import DfgNode +from api.models import TransactionHistoryStore +from api.util.time import combined_timestamp class WaitTimes(DfgNode): diff --git a/src/eateries/management/commands/export_db_snapshot.py b/src/api/management/commands/export_db_snapshot.py similarity index 96% rename from src/eateries/management/commands/export_db_snapshot.py rename to src/api/management/commands/export_db_snapshot.py index 5d87397..d2bc6a7 100644 --- a/src/eateries/management/commands/export_db_snapshot.py +++ b/src/api/management/commands/export_db_snapshot.py @@ -3,14 +3,14 @@ from datetime import datetime from pathlib import Path - -from util.constants import SnapshotFileName - -import eateries.models as models -import eateries.serializers as serializers import pytz import json + +from api.util.constants import SnapshotFileName +import api.models as models +import api.serializers as serializers + class Command(BaseCommand): help = 'Saves the current state of the database' diff --git a/src/eateries/management/commands/ingest_db_snapshot.py b/src/api/management/commands/ingest_db_snapshot.py similarity index 94% rename from src/eateries/management/commands/ingest_db_snapshot.py rename to src/api/management/commands/ingest_db_snapshot.py index 6fdc9b2..54db5f6 100644 --- a/src/eateries/management/commands/ingest_db_snapshot.py +++ b/src/api/management/commands/ingest_db_snapshot.py @@ -1,8 +1,7 @@ from django.core.management.base import BaseCommand -from util.constants import SnapshotFileName - -import eateries.serializers as serializers +from api.util.constants import SnapshotFileName +import api.serializers as serializers import json class Command(BaseCommand): diff --git a/src/eateries/management/commands/ingest_log_transactions.py b/src/api/management/commands/ingest_log_transactions.py similarity index 81% rename from src/eateries/management/commands/ingest_log_transactions.py rename to src/api/management/commands/ingest_log_transactions.py index f6bbb8b..f9daa59 100644 --- a/src/eateries/management/commands/ingest_log_transactions.py +++ b/src/api/management/commands/ingest_log_transactions.py @@ -1,11 +1,9 @@ -# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db - -import json from datetime import datetime from django.core.management.base import BaseCommand +import json -from eateries.controllers.create_transaction import CreateTransactionController -from eateries.controllers.delete_all_transactions import DeleteAllTransactionsController +from api.controllers.create_transaction import CreateTransactionController +from api.controllers.delete_all_transactions import DeleteAllTransactionsController class Command(BaseCommand): help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistoryStore table' @@ -14,6 +12,7 @@ def handle(self, *args, **options): num_deleted = DeleteAllTransactionsController().process() counter = 0 num_inserted = 0 + # Transaction Histories used to be stored in a giant log file. Ingest that log file into the db with open("../static_sources/data.log", "r") as log: for line in log: try: diff --git a/src/eateries/management/commands/ingest_recent_transactions.py b/src/api/management/commands/ingest_recent_transactions.py similarity index 93% rename from src/eateries/management/commands/ingest_recent_transactions.py rename to src/api/management/commands/ingest_recent_transactions.py index 7a6f2ed..ea55659 100644 --- a/src/eateries/management/commands/ingest_recent_transactions.py +++ b/src/api/management/commands/ingest_recent_transactions.py @@ -4,7 +4,7 @@ import os from requests.structures import CaseInsensitiveDict from django.core.management.base import BaseCommand -from eateries.controllers.create_transaction import CreateTransactionController +from api.controllers.create_transaction import CreateTransactionController class Command(BaseCommand): help = 'Fetches transaction data from a vendor API and adds it to our transaction history database' diff --git a/src/eateries/migrations/0001_initial.py b/src/api/migrations/0001_initial.py similarity index 89% rename from src/eateries/migrations/0001_initial.py rename to src/api/migrations/0001_initial.py index 25223e4..2984ce0 100644 --- a/src/eateries/migrations/0001_initial.py +++ b/src/api/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2022-01-13 15:03 +# Generated by Django 4.0 on 2022-01-13 17:27 from django.db import migrations, models import django.db.models.deletion @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=40)), ('description', models.CharField(blank=True, max_length=200)), ('base_price', models.FloatField(blank=True, null=True)), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), ], ), migrations.CreateModel( @@ -46,7 +46,7 @@ class Migration(migrations.Migration): ('canonical_date', models.DateField()), ('block_end_time', models.TimeField()), ('transaction_count', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), ], ), migrations.CreateModel( @@ -57,7 +57,7 @@ class Migration(migrations.Migration): ('total_price', models.FloatField(blank=True, null=True)), ('name', models.CharField(max_length=40)), ('item_subsection', models.CharField(max_length=40)), - ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), + ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.itemstore')), ], ), migrations.CreateModel( @@ -67,7 +67,7 @@ class Migration(migrations.Migration): ('type', models.CharField(max_length=200)), ('content', models.TextField()), ('created_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), ], ), migrations.CreateModel( @@ -75,7 +75,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.IntegerField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=40)), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), ], ), migrations.CreateModel( @@ -86,8 +86,8 @@ class Migration(migrations.Migration): ('day_of_week', models.CharField(choices=[('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday')], max_length=10)), ('start', models.TimeField()), ('end', models.TimeField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.menustore')), ], ), migrations.CreateModel( @@ -98,8 +98,8 @@ class Migration(migrations.Migration): ('canonical_date', models.DateField()), ('start_timestamp', models.IntegerField()), ('end_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.menustore')), ], options={ 'abstract': False, @@ -111,7 +111,7 @@ class Migration(migrations.Migration): ('id', models.IntegerField(primary_key=True, serialize=False)), ('event_description', models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], max_length=10)), ('canonical_date', models.DateField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), ], options={ 'abstract': False, @@ -122,15 +122,15 @@ class Migration(migrations.Migration): fields=[ ('id', models.IntegerField(primary_key=True, serialize=False)), ('category', models.CharField(max_length=40)), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.menustore')), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.menustore')), ], ), migrations.CreateModel( name='CategoryItemAssociation', fields=[ ('id', models.IntegerField(primary_key=True, serialize=False)), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.categorystore')), - ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.itemstore')), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.categorystore')), + ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.itemstore')), ], ), migrations.CreateModel( @@ -140,12 +140,12 @@ class Migration(migrations.Migration): ('description', models.CharField(max_length=250)), ('start_timestamp', models.IntegerField()), ('end_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eateries.eaterystore')), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), ], ), migrations.AddIndex( model_name='transactionhistorystore', - index=models.Index(fields=['canonical_date'], name='eateries_tr_canonic_31ef81_idx'), + index=models.Index(fields=['canonical_date'], name='api_transac_canonic_7e6d4f_idx'), ), migrations.AlterUniqueTogether( name='transactionhistorystore', diff --git a/src/eateries/__init__.py b/src/api/migrations/__init__.py similarity index 100% rename from src/eateries/__init__.py rename to src/api/migrations/__init__.py diff --git a/src/eateries/models/AlertModel.py b/src/api/models/AlertModel.py similarity index 86% rename from src/eateries/models/AlertModel.py rename to src/api/models/AlertModel.py index d4932b1..4908d57 100644 --- a/src/eateries/models/AlertModel.py +++ b/src/api/models/AlertModel.py @@ -1,5 +1,5 @@ from django.db import models -from eateries.models.EateryModel import EateryStore +from api.models.EateryModel import EateryStore class AlertStore(models.Model): id = models.IntegerField(primary_key=True) diff --git a/src/eateries/models/EateryModel.py b/src/api/models/EateryModel.py similarity index 100% rename from src/eateries/models/EateryModel.py rename to src/api/models/EateryModel.py diff --git a/src/eateries/models/EventScheduleModel.py b/src/api/models/EventScheduleModel.py similarity index 94% rename from src/eateries/models/EventScheduleModel.py rename to src/api/models/EventScheduleModel.py index 3965609..d85f82c 100644 --- a/src/eateries/models/EventScheduleModel.py +++ b/src/api/models/EventScheduleModel.py @@ -1,6 +1,6 @@ from django.db import models -from eateries.models.EateryModel import EateryStore -from eateries.models.MenuModel import MenuStore +from api.models.EateryModel import EateryStore +from api.models.MenuModel import MenuStore class EventDescription(models.TextChoices): BREAKFAST = 'Breakfast' diff --git a/src/eateries/models/MenuModel.py b/src/api/models/MenuModel.py similarity index 96% rename from src/eateries/models/MenuModel.py rename to src/api/models/MenuModel.py index 10efaba..af5fc87 100644 --- a/src/eateries/models/MenuModel.py +++ b/src/api/models/MenuModel.py @@ -1,5 +1,5 @@ from django.db import models -from eateries.models.EateryModel import EateryStore +from api.models.EateryModel import EateryStore class MenuStore(models.Model): id = models.IntegerField(primary_key=True) diff --git a/src/eateries/models/ReportModel.py b/src/api/models/ReportModel.py similarity index 83% rename from src/eateries/models/ReportModel.py rename to src/api/models/ReportModel.py index 3bb51d5..eea6e24 100644 --- a/src/eateries/models/ReportModel.py +++ b/src/api/models/ReportModel.py @@ -1,5 +1,5 @@ from django.db import models -from eateries.models.EateryModel import EateryStore +from api.models.EateryModel import EateryStore class ReportStore(models.Model): eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) diff --git a/src/eateries/models/TransactionModel.py b/src/api/models/TransactionModel.py similarity index 91% rename from src/eateries/models/TransactionModel.py rename to src/api/models/TransactionModel.py index 3b578b8..c29a5e2 100644 --- a/src/eateries/models/TransactionModel.py +++ b/src/api/models/TransactionModel.py @@ -1,5 +1,5 @@ from django.db import models -from eateries.models.EateryModel import EateryStore +from api.models.EateryModel import EateryStore # [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] class TransactionHistoryStore(models.Model): diff --git a/src/eateries/models/__init__.py b/src/api/models/__init__.py similarity index 79% rename from src/eateries/models/__init__.py rename to src/api/models/__init__.py index 420d31f..2ecaea6 100644 --- a/src/eateries/models/__init__.py +++ b/src/api/models/__init__.py @@ -1,5 +1,5 @@ # Need to expose models to django. -# In this app, models should be imported directly from eateries.models, not from eateries.models.package +# In this app, models should be imported directly from api.models, not from api.models.package from .AlertModel import AlertStore from .EateryModel import EateryStore diff --git a/src/eateries/serializers.py b/src/api/serializers.py similarity index 97% rename from src/eateries/serializers.py rename to src/api/serializers.py index 0dcc763..c0c8fbe 100644 --- a/src/eateries/serializers.py +++ b/src/api/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers -import eateries.models as models +import api.models as models class EateryStoreSerializer(serializers.ModelSerializer): class Meta: diff --git a/src/api/urls.py b/src/api/urls.py new file mode 100644 index 0000000..9083e65 --- /dev/null +++ b/src/api/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from api.views import MainDfgView, ReportView + +urlpatterns = [ + path("", MainDfgView.as_view(), name="main"), + path("report", ReportView.as_view(), name="report") +] diff --git a/src/util/constants.py b/src/api/util/constants.py similarity index 99% rename from src/util/constants.py rename to src/api/util/constants.py index ea047d0..b9108be 100644 --- a/src/util/constants.py +++ b/src/api/util/constants.py @@ -1,6 +1,6 @@ from enum import Enum -from dfg.datatype.Eatery import EateryID +from api.datatype.Eatery import EateryID CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" diff --git a/src/util/json.py b/src/api/util/json.py similarity index 96% rename from src/util/json.py rename to src/api/util/json.py index 4c96aec..9aa1876 100644 --- a/src/util/json.py +++ b/src/api/util/json.py @@ -1,7 +1,7 @@ from typing import Mapping from enum import Enum -from dfg.datatype.Eatery import EateryID +from api.datatype.Eatery import EateryID class FieldType(Enum): INT = 'int' diff --git a/src/util/time.py b/src/api/util/time.py similarity index 100% rename from src/util/time.py rename to src/api/util/time.py diff --git a/src/api/views.py b/src/api/views.py new file mode 100644 index 0000000..529990c --- /dev/null +++ b/src/api/views.py @@ -0,0 +1,36 @@ +from django.http import JsonResponse +from rest_framework.views import APIView +from datetime import date, timedelta +import pytz +import json + +from api.datatype.Eatery import EateryID +from api.dfg.main import main_dfg +from api.controllers.create_report import CreateReportController +from api.util.json import verify_json_fields, success_json, error_json, FieldType +# Create your views here. + +class MainDfgView(APIView): + dfg = main_dfg + def get(self, request): + tzinfo = pytz.timezone("US/Eastern") + reload = request.GET.get('reload') + result = self.dfg( + tzinfo=tzinfo, + reload=reload is not None and reload != "false", + start=date.today(), + end=date.today() + timedelta(days=7) + ) + return JsonResponse(result) + +class ReportView(APIView): + def post(self, request): + json_body = json.loads(request.body) + if not verify_json_fields(json_body, {"eatery_id": FieldType.INT, "type": FieldType.STRING, "content": FieldType.STRING}): + return JsonResponse(error_json("Malformed Request")) + CreateReportController( + eatery_id=EateryID(json_body["eatery_id"]), + type=json_body["type"], + content=json_body["content"] + ).process() + return JsonResponse(success_json("Reported")) \ No newline at end of file diff --git a/src/dfg/admin.py b/src/dfg/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/src/dfg/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/src/dfg/apps.py b/src/dfg/apps.py deleted file mode 100644 index 878e7d5..0000000 --- a/src/dfg/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class ApiConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "api" diff --git a/src/dfg/tests.py b/src/dfg/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/dfg/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/dfg/views.py b/src/dfg/views.py deleted file mode 100644 index 8fa2101..0000000 --- a/src/dfg/views.py +++ /dev/null @@ -1,68 +0,0 @@ -from datetime import date, timedelta - -import pytz -from django.http import JsonResponse -from rest_framework.views import APIView - -from dfg.nodes.CornellDiningNow import CornellDiningNow -from dfg.nodes.EateryStubs import EateryStubs -from dfg.nodes.EateriesFromDB import EateriesFromDB -from dfg.nodes.macros.EateryEvents import EateryEvents - -from dfg.nodes.system.DictResponseWrapper import DictResponseWrapper -from dfg.nodes.system.ConvertToJson import ConvertToJson -from dfg.nodes.system.EateryGenerator import EateryGenerator -from dfg.nodes.system.InMemoryCache import InMemoryCache -from dfg.nodes.system.Mapping import Mapping - -from dfg.nodes.macros.LeftMergeEateries import LeftMergeEateries - -from dfg.nodes.wait_times.WaitTimes import WaitTimes -from dfg.nodes.wait_times.WaitTimeFilter import WaitTimeFilter - -class MainDfgView(APIView): - main_dfg = DictResponseWrapper( - ConvertToJson( - InMemoryCache( - WaitTimeFilter( - LeftMergeEateries( - Mapping( - child=EateryStubs(), - fn = lambda eatery, cache: EateryGenerator( - eatery_id=eatery.id, - wait_times_dfg=WaitTimes(eatery.id, cache) - ) - ), - LeftMergeEateries( - Mapping( - child = EateryStubs(), - fn = lambda eatery, cache: EateryGenerator( - eatery_id=eatery.id, - events_dfg=EateryEvents(eatery.id, cache) - ) - ), - LeftMergeEateries( - EateriesFromDB(), - LeftMergeEateries( - CornellDiningNow(), - EateryStubs() - ) - ) - ) - ) - ) - ) - ), - re_raise_exceptions=True - ) - - def get(self, request): - tzinfo = pytz.timezone("US/Eastern") - reload = request.GET.get('reload') - result = self.main_dfg( - tzinfo=tzinfo, - reload=reload is not None and reload != "false", - start=date.today(), - end=date.today() + timedelta(days=7) - ) - return JsonResponse(result) \ No newline at end of file diff --git a/src/eateries/migrations/__init__.py b/src/eateries/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/eateries/tests.py b/src/eateries/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/eateries/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/eateries/views.py b/src/eateries/views.py deleted file mode 100644 index 2ddedd6..0000000 --- a/src/eateries/views.py +++ /dev/null @@ -1,21 +0,0 @@ -from django.shortcuts import render -from django.http import JsonResponse -from dfg.datatype.Eatery import EateryID -from eateries.controllers.create_report import CreateReportController -from rest_framework.views import APIView - -from util.json import verify_json_fields, success_json, error_json, FieldType -import json -# Create your views here. - -class ReportView(APIView): - def post(self, request): - json_body = json.loads(request.body) - if not verify_json_fields(json_body, {"eatery_id": FieldType.INT, "type": FieldType.STRING, "content": FieldType.STRING}): - return JsonResponse(error_json("Malformed Request")) - CreateReportController( - eatery_id=EateryID(json_body["eatery_id"]), - type=json_body["type"], - content=json_body["content"] - ).process() - return JsonResponse(success_json("Reported")) \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index ffc709f..f11ca9a 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -38,7 +38,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "eateries", + "api", "rest_framework" ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index b2d83e7..eedc5d5 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -16,15 +16,7 @@ from django.contrib import admin from django.urls import include, path -from dfg.views import MainDfgView -from eateries.views import ReportView - -api = [ - path("", MainDfgView.as_view(), name="main"), - path("report", ReportView.as_view(), name="report") -] - urlpatterns = [ - path("api/", include(api)), + path("api/", include("api.urls")), path("admin/", admin.site.urls), ] From b3a80375d80857c4ec29bbb21dc12fd440d1f82e Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 13 Jan 2022 12:31:48 -0500 Subject: [PATCH 052/305] Updated descriptions of dfg nodes --- src/api/dfg/nodes/EateriesFromDB.py | 3 +++ src/api/dfg/nodes/macros/EateryEvents.py | 2 +- src/api/dfg/nodes/schedule/DateSchedule.py | 2 +- src/api/dfg/nodes/system/ConvertFromJson.py | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/api/dfg/nodes/EateriesFromDB.py b/src/api/dfg/nodes/EateriesFromDB.py index 6bcf9ee..c333289 100644 --- a/src/api/dfg/nodes/EateriesFromDB.py +++ b/src/api/dfg/nodes/EateriesFromDB.py @@ -56,5 +56,8 @@ def alert_from_serialized(serialized_alert: dict): end_timestamp=serialized_alert["end_timestamp"] ) + def description(self): + return "EateriesFromDB" + diff --git a/src/api/dfg/nodes/macros/EateryEvents.py b/src/api/dfg/nodes/macros/EateryEvents.py index ebfa9d1..460403d 100644 --- a/src/api/dfg/nodes/macros/EateryEvents.py +++ b/src/api/dfg/nodes/macros/EateryEvents.py @@ -36,4 +36,4 @@ def __call__(self, *args, **kwargs): return self.macro(*args, **kwargs) def description(self): - return "LeftMergeEvents" \ No newline at end of file + return "EateryEvents" \ No newline at end of file diff --git a/src/api/dfg/nodes/schedule/DateSchedule.py b/src/api/dfg/nodes/schedule/DateSchedule.py index 84ce774..53d4476 100644 --- a/src/api/dfg/nodes/schedule/DateSchedule.py +++ b/src/api/dfg/nodes/schedule/DateSchedule.py @@ -13,4 +13,4 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: return [] def description(self): - return "EateryStubs" + return "DateSchedule" diff --git a/src/api/dfg/nodes/system/ConvertFromJson.py b/src/api/dfg/nodes/system/ConvertFromJson.py index bcf8067..ba340a5 100644 --- a/src/api/dfg/nodes/system/ConvertFromJson.py +++ b/src/api/dfg/nodes/system/ConvertFromJson.py @@ -52,4 +52,4 @@ def from_json(obj: Union[list, dict], *args, **kwargs): return Event.from_json(obj) def description(self): - return "EventFromJson" + return "ConvertFromJson" From 1249637ce596c5bae2d22d09cef34fdea2f35405 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 13 Jan 2022 13:20:47 -0500 Subject: [PATCH 053/305] Update ingest_db_snapshot.py --- .../management/commands/ingest_db_snapshot.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/api/management/commands/ingest_db_snapshot.py b/src/api/management/commands/ingest_db_snapshot.py index 54db5f6..d4b4e60 100644 --- a/src/api/management/commands/ingest_db_snapshot.py +++ b/src/api/management/commands/ingest_db_snapshot.py @@ -5,11 +5,13 @@ import json class Command(BaseCommand): - help = 'Overrides current state of the db with a db snapshot' + help = 'Overrides current state of the db with a db snapshot. Takes --file argument' + + def add_arguments(self, parser): + parser.add_argument('--input', type=str) # Only writes data if the table has been flushed - def ingest_data(self, serializer, file_name: SnapshotFileName): - folder_path = "/Users/connorreinhold/Documents/AppDev/new-eatery/eatery-blue-backend/src/db_snapshots/2022-01-11 15:18:05" + def ingest_data(self, serializer, folder_path: str, file_name: SnapshotFileName): with open(f"{folder_path}/{file_name.value}", "r") as file: json_objs = [] for line in file: @@ -20,11 +22,12 @@ def ingest_data(self, serializer, file_name: SnapshotFileName): serialized_objs.save() def handle(self, *args, **options): - self.ingest_data(serializers.EateryStoreSerializer, SnapshotFileName.EATERY_STORE) - self.ingest_data(serializers.AlertStoreSerializer, SnapshotFileName.ALERT_STORE) - self.ingest_data(serializers.MenuStoreSerializer, SnapshotFileName.MENU_STORE) - self.ingest_data(serializers.CategoryStoreSerializer, SnapshotFileName.CATEGORY_STORE) - self.ingest_data(serializers.ItemStoreSerializer, SnapshotFileName.ITEM_STORE) - self.ingest_data(serializers.SubItemStoreSerializer, SnapshotFileName.SUBITEM_STORE) - self.ingest_data(serializers.CategoryItemAssociationSerializer, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) - self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) \ No newline at end of file + folder_path = options["input"] + self.ingest_data(serializers.EateryStoreSerializer, folder_path, SnapshotFileName.EATERY_STORE) + self.ingest_data(serializers.AlertStoreSerializer, folder_path, SnapshotFileName.ALERT_STORE) + self.ingest_data(serializers.MenuStoreSerializer, folder_path, SnapshotFileName.MENU_STORE) + self.ingest_data(serializers.CategoryStoreSerializer, folder_path, SnapshotFileName.CATEGORY_STORE) + self.ingest_data(serializers.ItemStoreSerializer, folder_path, SnapshotFileName.ITEM_STORE) + self.ingest_data(serializers.SubItemStoreSerializer, folder_path, SnapshotFileName.SUBITEM_STORE) + self.ingest_data(serializers.CategoryItemAssociationSerializer, folder_path, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) + self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, folder_path, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) \ No newline at end of file From c880556c362140916ff755f14b171b2aed2125c8 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 13 Jan 2022 13:23:59 -0500 Subject: [PATCH 054/305] Remove print statement --- src/api/util/json.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/util/json.py b/src/api/util/json.py index 9aa1876..aec6d35 100644 --- a/src/api/util/json.py +++ b/src/api/util/json.py @@ -11,7 +11,6 @@ class FieldType(Enum): def verify_json_fields(json, field_type_map: Mapping[str, FieldType]): for field in field_type_map: - print(field) if field not in json: return False if field_type_map[field] is FieldType.INT: From 0eeb13b7ebe8b3aec24e784224f772cb84a0e450 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 13 Jan 2022 20:33:04 -0500 Subject: [PATCH 055/305] Fix NoneType error with Eatery --- api/datatype/Eatery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/datatype/Eatery.py b/api/datatype/Eatery.py index 767987e..01ab28c 100644 --- a/api/datatype/Eatery.py +++ b/api/datatype/Eatery.py @@ -98,7 +98,7 @@ def to_json( end: Optional[date] = None ): eatery = { - "id": self.id.value, + "id": None if self.id is None else self.id.value, "name": self.name, "image_url": self.image_url, "menu_summary": self.menu_summary, From 288160f41ea831226b9d84ae7df85f4d592acd9f Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 13 Jan 2022 20:37:20 -0500 Subject: [PATCH 056/305] Fix NoneType bug #2 --- src/api/dfg/nodes/macros/LeftMergeEateries.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/dfg/nodes/macros/LeftMergeEateries.py b/src/api/dfg/nodes/macros/LeftMergeEateries.py index ab6b544..cc621d2 100644 --- a/src/api/dfg/nodes/macros/LeftMergeEateries.py +++ b/src/api/dfg/nodes/macros/LeftMergeEateries.py @@ -9,6 +9,10 @@ def __init__(self, left: DfgNode, right: DfgNode): def comparator(left, right): if left["id"] == right["id"]: return 0 + elif left["id"] == None: + return -1 + elif right["id"] == None: + return 1 elif left["id"] < right["id"]: return -1 else: From 531728101aeec4c589b9b6ab490898f19433e996 Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Thu, 13 Jan 2022 20:43:29 -0500 Subject: [PATCH 057/305] Add Morrison Dining EateryID --- src/api/datatype/Eatery.py | 3 ++- src/api/util/constants.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/api/datatype/Eatery.py b/src/api/datatype/Eatery.py index 3889192..d8102a6 100644 --- a/src/api/datatype/Eatery.py +++ b/src/api/datatype/Eatery.py @@ -48,6 +48,7 @@ class EateryID(Enum): GIMME_COFFEE = 36 LOUIES = 37 ANABELS_GROCERY = 38 + MORRISON_DINING = 39 class Eatery: @@ -122,7 +123,7 @@ def to_json( @staticmethod def from_json(eatery_json): return Eatery( - id=None if "id" not in eatery_json else EateryID(eatery_json["id"]), + id=None if "id" not in eatery_json or eatery_json["id"] is None else EateryID(eatery_json["id"]), name=eatery_json.get("name"), image_url=eatery_json.get("image_url"), menu_summary=eatery_json.get("menu_summary"), diff --git a/src/api/util/constants.py b/src/api/util/constants.py index b9108be..a32ab7a 100644 --- a/src/api/util/constants.py +++ b/src/api/util/constants.py @@ -83,6 +83,8 @@ def dining_id_to_internal_id(id: int): return EateryID.STRAIGHT_FROM_THE_MARKET elif id == 23: return EateryID.TRILLIUM + elif id == 43: + return EateryID.MORRISON_DINING else: return None From 033e4a58f6b7922b40f7933a09ea242e5dfdf37a Mon Sep 17 00:00:00 2001 From: connorreinhold Date: Fri, 14 Jan 2022 08:34:46 -0500 Subject: [PATCH 058/305] ingest_log_transactions takes file as input --- src/api/management/commands/ingest_log_transactions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/api/management/commands/ingest_log_transactions.py b/src/api/management/commands/ingest_log_transactions.py index f9daa59..c0e14b3 100644 --- a/src/api/management/commands/ingest_log_transactions.py +++ b/src/api/management/commands/ingest_log_transactions.py @@ -8,12 +8,16 @@ class Command(BaseCommand): help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistoryStore table' + def add_arguments(self, parser): + parser.add_argument('--input', type=str) + def handle(self, *args, **options): num_deleted = DeleteAllTransactionsController().process() counter = 0 num_inserted = 0 + file_path = options["input"] # Transaction Histories used to be stored in a giant log file. Ingest that log file into the db - with open("../static_sources/data.log", "r") as log: + with open(file_path, "r") as log: for line in log: try: data = json.loads(line) From 6aa103e647f58c75b08b537fe817ef407d81e1fc Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Fri, 4 Mar 2022 01:30:28 -0500 Subject: [PATCH 059/305] Implement update endpoint for text params, define allowed fields, and add image encoding --- .gitignore | 1 + src/api/controllers/create_transaction.py | 11 +++-- src/api/controllers/update_eatery.py | 46 +++++++++++++++++++ .../management/commands/ingest_db_snapshot.py | 29 ++++++++---- src/api/models/TransactionModel.py | 6 ++- src/api/urls.py | 3 +- src/api/views.py | 23 +++++++++- src/manage.py | 3 +- start.sh | 3 ++ 9 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 src/api/controllers/update_eatery.py create mode 100644 start.sh diff --git a/.gitignore b/.gitignore index ae6a0a5..89ace21 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ __pycache__/ # More Python stuff .DS_Store +.vscode/ env/ .Python build/ diff --git a/src/api/controllers/create_transaction.py b/src/api/controllers/create_transaction.py index db70b6f..86cba00 100644 --- a/src/api/controllers/create_transaction.py +++ b/src/api/controllers/create_transaction.py @@ -4,6 +4,7 @@ from api.models import TransactionHistoryStore from api.util.constants import vendor_name_to_internal_id + class CreateTransactionController: def __init__(self, data): @@ -13,7 +14,8 @@ def process(self): if self._data["TIMESTAMP"] == "Invalid date": return 0 tz = pytz.timezone('America/New_York') - recent_datetime = tz.localize(datetime.strptime(self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p')) + recent_datetime = tz.localize(datetime.strptime( + self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p')) canonical_date = recent_datetime.date() block_end_time = recent_datetime.time() if recent_datetime.hour < 4: @@ -29,13 +31,12 @@ def process(self): num_inserted += 1 try: TransactionHistoryStore.objects.create( - eatery_id = internal_id, - canonical_date = canonical_date, - block_end_time = block_end_time, + eatery_id=internal_id, + canonical_date=canonical_date, + block_end_time=block_end_time, transaction_count=place["CROWD_COUNT"] ) except Exception as e: # print(e) num_inserted -= 1 return num_inserted - diff --git a/src/api/controllers/update_eatery.py b/src/api/controllers/update_eatery.py new file mode 100644 index 0000000..9024e09 --- /dev/null +++ b/src/api/controllers/update_eatery.py @@ -0,0 +1,46 @@ +from django.http import QueryDict +from api.models import EateryStore +import base64 +import asyncio + + +class UpdateEateryController: + def __init__(self, id: int, update_map: QueryDict, image): + ''' + Update_map is a dictionary that maps the fields we want to update to + the values we want to map them to + + Requires: id is a valid id and all keys in update_map are valid columns + ''' + self.id = id + self.update_data = {} + allowed_fields = ["name", "menu_summary", "location", "campus_area", "online_order_url", "latitude", + "longitude", "payment_accepts_meal_swipes", "payment_accepts_brbs", "payment_accepts_cash"] + + if image is not None: + asyncio.run(self.upload_image(image)) + + for key, val in update_map.items(): + if key in allowed_fields: + self.update_data[key] = val + + async def upload_image(self, image): + ''' + Helper method that asynchronously uploads image bytes to assets repo + Returns: stored image URL + ''' + + b64_encoded_image = b"" + # Encodes bytes in chunks to handle large image files efficiently + for chunk in image.chunks(): + b64_encoded_image += base64.b64encode(chunk) + + print(b64_encoded_image) + + def process(self): + ''' + Selects the DB entry we want to update and updates it using provided data + ''' + + # Uses double-splat to map stored update dict to kwargs + EateryStore.objects.filter(id=self.id).update(**self.update_data) diff --git a/src/api/management/commands/ingest_db_snapshot.py b/src/api/management/commands/ingest_db_snapshot.py index d4b4e60..40b5c72 100644 --- a/src/api/management/commands/ingest_db_snapshot.py +++ b/src/api/management/commands/ingest_db_snapshot.py @@ -4,15 +4,16 @@ import api.serializers as serializers import json + class Command(BaseCommand): - help = 'Overrides current state of the db with a db snapshot. Takes --file argument' + help = 'Overrides current state of the db with a db snapshot. Takes --input argument' def add_arguments(self, parser): parser.add_argument('--input', type=str) # Only writes data if the table has been flushed def ingest_data(self, serializer, folder_path: str, file_name: SnapshotFileName): - with open(f"{folder_path}/{file_name.value}", "r") as file: + with open(f"{folder_path}/{file_name.value}", "r") as file: json_objs = [] for line in file: if (len(line) > 2): @@ -23,11 +24,19 @@ def ingest_data(self, serializer, folder_path: str, file_name: SnapshotFileName) def handle(self, *args, **options): folder_path = options["input"] - self.ingest_data(serializers.EateryStoreSerializer, folder_path, SnapshotFileName.EATERY_STORE) - self.ingest_data(serializers.AlertStoreSerializer, folder_path, SnapshotFileName.ALERT_STORE) - self.ingest_data(serializers.MenuStoreSerializer, folder_path, SnapshotFileName.MENU_STORE) - self.ingest_data(serializers.CategoryStoreSerializer, folder_path, SnapshotFileName.CATEGORY_STORE) - self.ingest_data(serializers.ItemStoreSerializer, folder_path, SnapshotFileName.ITEM_STORE) - self.ingest_data(serializers.SubItemStoreSerializer, folder_path, SnapshotFileName.SUBITEM_STORE) - self.ingest_data(serializers.CategoryItemAssociationSerializer, folder_path, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) - self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, folder_path, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) \ No newline at end of file + self.ingest_data(serializers.EateryStoreSerializer, + folder_path, SnapshotFileName.EATERY_STORE) + self.ingest_data(serializers.AlertStoreSerializer, + folder_path, SnapshotFileName.ALERT_STORE) + self.ingest_data(serializers.MenuStoreSerializer, + folder_path, SnapshotFileName.MENU_STORE) + self.ingest_data(serializers.CategoryStoreSerializer, + folder_path, SnapshotFileName.CATEGORY_STORE) + self.ingest_data(serializers.ItemStoreSerializer, + folder_path, SnapshotFileName.ITEM_STORE) + self.ingest_data(serializers.SubItemStoreSerializer, + folder_path, SnapshotFileName.SUBITEM_STORE) + self.ingest_data(serializers.CategoryItemAssociationSerializer, + folder_path, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) + self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, + folder_path, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) diff --git a/src/api/models/TransactionModel.py b/src/api/models/TransactionModel.py index c29a5e2..a5a537f 100644 --- a/src/api/models/TransactionModel.py +++ b/src/api/models/TransactionModel.py @@ -2,11 +2,13 @@ from api.models.EateryModel import EateryStore # [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] + + class TransactionHistoryStore(models.Model): class Meta: unique_together = ('eatery_id', 'block_end_time', 'canonical_date') - indexes = [models.Index(fields = ['canonical_date'])] + indexes = [models.Index(fields=['canonical_date'])] eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) canonical_date = models.DateField() block_end_time = models.TimeField() - transaction_count = models.IntegerField() \ No newline at end of file + transaction_count = models.IntegerField() diff --git a/src/api/urls.py b/src/api/urls.py index 9083e65..a5e8d4f 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -1,8 +1,9 @@ from django.urls import path -from api.views import MainDfgView, ReportView +from api.views import MainDfgView, UpdateView, ReportView urlpatterns = [ path("", MainDfgView.as_view(), name="main"), + path("update", UpdateView.as_view(), name="update"), path("report", ReportView.as_view(), name="report") ] diff --git a/src/api/views.py b/src/api/views.py index 529990c..7d574ad 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -7,11 +7,14 @@ from api.datatype.Eatery import EateryID from api.dfg.main import main_dfg from api.controllers.create_report import CreateReportController +from api.controllers.update_eatery import UpdateEateryController from api.util.json import verify_json_fields, success_json, error_json, FieldType # Create your views here. + class MainDfgView(APIView): dfg = main_dfg + def get(self, request): tzinfo = pytz.timezone("US/Eastern") reload = request.GET.get('reload') @@ -23,6 +26,7 @@ def get(self, request): ) return JsonResponse(result) + class ReportView(APIView): def post(self, request): json_body = json.loads(request.body) @@ -33,4 +37,21 @@ def post(self, request): type=json_body["type"], content=json_body["content"] ).process() - return JsonResponse(success_json("Reported")) \ No newline at end of file + return JsonResponse(success_json("Reported")) + + +class UpdateView(APIView): + def post(self, request): + text_params = request.POST + try: + image_param = request.FILES.get("image") + print(image_param) + except: + image_param = None + # Will add JSON verification here + UpdateEateryController( + text_params.get("id"), + text_params, + image_param + ).process() + return JsonResponse(success_json("Updated")) diff --git a/src/manage.py b/src/manage.py index b469b27..ad32ebf 100755 --- a/src/manage.py +++ b/src/manage.py @@ -6,7 +6,8 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", + "eatery_blue_backend.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..8994c63 --- /dev/null +++ b/start.sh @@ -0,0 +1,3 @@ +source env/bin/activate +source .env +python3 src/manage.py runserver 0.0.0.0:8000 \ No newline at end of file From 2edc3d389dc8a3de4ba32c6bf69ff0bc3f21e24d Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sat, 5 Mar 2022 23:03:23 -0500 Subject: [PATCH 060/305] Added image uploading and exception handling --- src/api/controllers/update_eatery.py | 54 +++++++++++++++++++--------- src/api/views.py | 32 ++++++++++------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/api/controllers/update_eatery.py b/src/api/controllers/update_eatery.py index 9024e09..6564eb2 100644 --- a/src/api/controllers/update_eatery.py +++ b/src/api/controllers/update_eatery.py @@ -1,46 +1,68 @@ from django.http import QueryDict from api.models import EateryStore import base64 -import asyncio +import requests +import os class UpdateEateryController: def __init__(self, id: int, update_map: QueryDict, image): - ''' - Update_map is a dictionary that maps the fields we want to update to + """ + Update_map is a dictionary that maps the fields we want to update to the values we want to map them to - Requires: id is a valid id and all keys in update_map are valid columns - ''' + Requires: id is a valid id and all keys in update_map are valid fields + in the EateryStore class + Raises: Exception when invalid keys are provided + """ self.id = id self.update_data = {} - allowed_fields = ["name", "menu_summary", "location", "campus_area", "online_order_url", "latitude", - "longitude", "payment_accepts_meal_swipes", "payment_accepts_brbs", "payment_accepts_cash"] if image is not None: - asyncio.run(self.upload_image(image)) + img_url = self.upload_image(image) + self.update_data["image_url"] = img_url for key, val in update_map.items(): - if key in allowed_fields: + try: self.update_data[key] = val + except: + raise Exception("Invalid update field(s) provided") - async def upload_image(self, image): - ''' + def upload_image(self, image): + """ Helper method that asynchronously uploads image bytes to assets repo Returns: stored image URL - ''' + Raises: Exception invalid file extension when a non jpg, jpeg, gif, or png is provided + """ + + valid_extensions = ["jpg", "jpeg", "gif", "png"] + extension = (str(image)).split(".") + extension = extension[len(extension) - 1] + + if extension not in valid_extensions: + raise Exception("Invalid File Extension") b64_encoded_image = b"" # Encodes bytes in chunks to handle large image files efficiently for chunk in image.chunks(): b64_encoded_image += base64.b64encode(chunk) - print(b64_encoded_image) + b64_encoded_image = b64_encoded_image.decode("utf-8") + + response = requests.post( + "https://upload.cornellappdev.com/upload/", + json={ + "bucket": os.environ["IMAGE_BUCKET"], + "image": f"data:image/{extension};base64,{b64_encoded_image}", + }, + ) + + return response.json()["data"] def process(self): - ''' - Selects the DB entry we want to update and updates it using provided data - ''' + """ + Selects DB entry we want to update and updates it using provided data + """ # Uses double-splat to map stored update dict to kwargs EateryStore.objects.filter(id=self.id).update(**self.update_data) diff --git a/src/api/views.py b/src/api/views.py index 7d574ad..64ee088 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -9,6 +9,7 @@ from api.controllers.create_report import CreateReportController from api.controllers.update_eatery import UpdateEateryController from api.util.json import verify_json_fields, success_json, error_json, FieldType + # Create your views here. @@ -17,12 +18,12 @@ class MainDfgView(APIView): def get(self, request): tzinfo = pytz.timezone("US/Eastern") - reload = request.GET.get('reload') + reload = request.GET.get("reload") result = self.dfg( tzinfo=tzinfo, reload=reload is not None and reload != "false", start=date.today(), - end=date.today() + timedelta(days=7) + end=date.today() + timedelta(days=7), ) return JsonResponse(result) @@ -30,12 +31,19 @@ def get(self, request): class ReportView(APIView): def post(self, request): json_body = json.loads(request.body) - if not verify_json_fields(json_body, {"eatery_id": FieldType.INT, "type": FieldType.STRING, "content": FieldType.STRING}): + if not verify_json_fields( + json_body, + { + "eatery_id": FieldType.INT, + "type": FieldType.STRING, + "content": FieldType.STRING, + }, + ): return JsonResponse(error_json("Malformed Request")) CreateReportController( eatery_id=EateryID(json_body["eatery_id"]), type=json_body["type"], - content=json_body["content"] + content=json_body["content"], ).process() return JsonResponse(success_json("Reported")) @@ -45,13 +53,13 @@ def post(self, request): text_params = request.POST try: image_param = request.FILES.get("image") - print(image_param) except: image_param = None - # Will add JSON verification here - UpdateEateryController( - text_params.get("id"), - text_params, - image_param - ).process() - return JsonResponse(success_json("Updated")) + + try: + UpdateEateryController( + text_params.get("id"), text_params, image_param + ).process() + return JsonResponse(success_json("Updated")) + except Exception as e: + return JsonResponse(error_json(str(e))) From b878f5c64060f4aac5a9cc335c3c8e0a367fa896 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 6 Mar 2022 14:49:13 -0500 Subject: [PATCH 061/305] Deal with upload api failures --- src/api/controllers/update_eatery.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/api/controllers/update_eatery.py b/src/api/controllers/update_eatery.py index 6564eb2..c318e3d 100644 --- a/src/api/controllers/update_eatery.py +++ b/src/api/controllers/update_eatery.py @@ -1,5 +1,6 @@ from django.http import QueryDict from api.models import EateryStore +from mimetypes import guess_extension, guess_type import base64 import requests import os @@ -38,6 +39,8 @@ def upload_image(self, image): valid_extensions = ["jpg", "jpeg", "gif", "png"] extension = (str(image)).split(".") extension = extension[len(extension) - 1] + if extension == "jpg": + extension = "jpeg" if extension not in valid_extensions: raise Exception("Invalid File Extension") @@ -57,7 +60,10 @@ def upload_image(self, image): }, ) - return response.json()["data"] + try: + return response.json()["data"] + except: + raise Exception("Image uploading unsuccessful") def process(self): """ From 178399228f9042a2858d4f63381a20b7cc43c734 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 6 Mar 2022 14:55:58 -0500 Subject: [PATCH 062/305] Added back field constraints --- src/api/controllers/update_eatery.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/api/controllers/update_eatery.py b/src/api/controllers/update_eatery.py index c318e3d..2df96a5 100644 --- a/src/api/controllers/update_eatery.py +++ b/src/api/controllers/update_eatery.py @@ -1,6 +1,5 @@ from django.http import QueryDict from api.models import EateryStore -from mimetypes import guess_extension, guess_type import base64 import requests import os @@ -13,7 +12,7 @@ def __init__(self, id: int, update_map: QueryDict, image): the values we want to map them to Requires: id is a valid id and all keys in update_map are valid fields - in the EateryStore class + in the EateryStore class (except username/password cannot be provided) Raises: Exception when invalid keys are provided """ self.id = id @@ -23,8 +22,26 @@ def __init__(self, id: int, update_map: QueryDict, image): img_url = self.upload_image(image) self.update_data["image_url"] = img_url + allowed_fields = [ + "id", + "name", + "menu_summary", + "image_url", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + ] + for key, val in update_map.items(): try: + if key not in allowed_fields: + print("e") + raise Exception self.update_data[key] = val except: raise Exception("Invalid update field(s) provided") From 4ad7a86628452f7f40916afa3ff9972744028749 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 6 Mar 2022 14:58:15 -0500 Subject: [PATCH 063/305] Remove image url allowed fields to enable only uploading --- src/api/controllers/update_eatery.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/controllers/update_eatery.py b/src/api/controllers/update_eatery.py index 2df96a5..e923741 100644 --- a/src/api/controllers/update_eatery.py +++ b/src/api/controllers/update_eatery.py @@ -26,7 +26,6 @@ def __init__(self, id: int, update_map: QueryDict, image): "id", "name", "menu_summary", - "image_url", "location", "campus_area", "online_order_url", From 2049f85b2fcc63738485fab9ff4e91c591812d86 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 6 Mar 2022 14:59:30 -0500 Subject: [PATCH 064/305] Make docs clearer --- src/api/controllers/update_eatery.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api/controllers/update_eatery.py b/src/api/controllers/update_eatery.py index e923741..d8acf8d 100644 --- a/src/api/controllers/update_eatery.py +++ b/src/api/controllers/update_eatery.py @@ -12,7 +12,8 @@ def __init__(self, id: int, update_map: QueryDict, image): the values we want to map them to Requires: id is a valid id and all keys in update_map are valid fields - in the EateryStore class (except username/password cannot be provided) + in the EateryStore class (except username/password cannot be provided), + as well as an optional image field containing an image file to be uploaded Raises: Exception when invalid keys are provided """ self.id = id From 129eca652363fe7a07920f50b131067358095c39 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 6 Mar 2022 19:53:14 -0500 Subject: [PATCH 065/305] Raised new exception for bad id --- src/api/controllers/update_eatery.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/api/controllers/update_eatery.py b/src/api/controllers/update_eatery.py index d8acf8d..e9a5cc5 100644 --- a/src/api/controllers/update_eatery.py +++ b/src/api/controllers/update_eatery.py @@ -40,7 +40,6 @@ def __init__(self, id: int, update_map: QueryDict, image): for key, val in update_map.items(): try: if key not in allowed_fields: - print("e") raise Exception self.update_data[key] = val except: @@ -87,5 +86,7 @@ def process(self): Selects DB entry we want to update and updates it using provided data """ - # Uses double-splat to map stored update dict to kwargs - EateryStore.objects.filter(id=self.id).update(**self.update_data) + supplied_eatery = EateryStore.objects.filter(id=self.id) + if list(supplied_eatery) == []: + raise Exception("Invalid eatery ID supplied") + supplied_eatery.update(**self.update_data) From 2e3ca5b6cfa499c27fa7b85bf5dde19654e3855b Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 6 Mar 2022 20:28:24 -0500 Subject: [PATCH 066/305] Implemented validation with enums --- src/api/controllers/update_eatery.py | 10 ++++------ src/api/views.py | 9 ++++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/api/controllers/update_eatery.py b/src/api/controllers/update_eatery.py index e9a5cc5..8a8d4c4 100644 --- a/src/api/controllers/update_eatery.py +++ b/src/api/controllers/update_eatery.py @@ -1,4 +1,5 @@ from django.http import QueryDict +from api.datatype.Eatery import EateryID from api.models import EateryStore import base64 import requests @@ -6,7 +7,7 @@ class UpdateEateryController: - def __init__(self, id: int, update_map: QueryDict, image): + def __init__(self, id: EateryID, update_map: QueryDict, image): """ Update_map is a dictionary that maps the fields we want to update to the values we want to map them to @@ -85,8 +86,5 @@ def process(self): """ Selects DB entry we want to update and updates it using provided data """ - - supplied_eatery = EateryStore.objects.filter(id=self.id) - if list(supplied_eatery) == []: - raise Exception("Invalid eatery ID supplied") - supplied_eatery.update(**self.update_data) + # use double-splat to convert dict to kwargs + EateryStore.objects.filter(id=self.id.value).update(**self.update_data) diff --git a/src/api/views.py b/src/api/views.py index 64ee088..48b3f9d 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -57,9 +57,12 @@ def post(self, request): image_param = None try: - UpdateEateryController( - text_params.get("id"), text_params, image_param - ).process() + id = int(text_params.get("id")) + except: + return JsonResponse(error_json("ID must be castable to an int")) + + try: + UpdateEateryController(EateryID(id), text_params, image_param).process() return JsonResponse(success_json("Updated")) except Exception as e: return JsonResponse(error_json(str(e))) From 9cfaeb91fd2120042a7262fe368a0a1c7368ae5e Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Tue, 8 Mar 2022 15:33:28 -0500 Subject: [PATCH 067/305] Add postgres settings and remove eatery 9 --- src/api/datatype/Eatery.py | 82 +++++++++++++++++------------ src/eatery_blue_backend/settings.py | 26 ++++++--- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/src/api/datatype/Eatery.py b/src/api/datatype/Eatery.py index d8102a6..d21e536 100644 --- a/src/api/datatype/Eatery.py +++ b/src/api/datatype/Eatery.py @@ -18,7 +18,6 @@ class EateryID(Enum): BIG_RED_BARN = 6 BUS_STOP_BAGELS = 7 CAFE_JENNIE = 8 - CAROLS_CAFE = 9 COOK_HOUSE = 10 DAIRY_BAR = 11 CROSSINGS_CAFE = 12 @@ -50,25 +49,25 @@ class EateryID(Enum): ANABELS_GROCERY = 38 MORRISON_DINING = 39 -class Eatery: +class Eatery: def __init__( - self, - id: EateryID, - name: Optional[str] = None, - image_url: Optional[str] = None, - menu_summary: Optional[str] = None, - campus_area: Optional[str] = None, - events: Optional[list[Event]] = None, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - payment_accepts_cash: Optional[bool] = None, - payment_accepts_brbs: Optional[bool] = None, - payment_accepts_meal_swipes: Optional[bool] = None, - location: Optional[str] = None, - online_order_url: Optional[str] = None, - wait_times: Optional[list[WaitTimesDay]] = None, - alerts: Optional[list[EateryAlert]] = None + self, + id: EateryID, + name: Optional[str] = None, + image_url: Optional[str] = None, + menu_summary: Optional[str] = None, + campus_area: Optional[str] = None, + events: Optional[list[Event]] = None, + latitude: Optional[float] = None, + longitude: Optional[float] = None, + payment_accepts_cash: Optional[bool] = None, + payment_accepts_brbs: Optional[bool] = None, + payment_accepts_meal_swipes: Optional[bool] = None, + location: Optional[str] = None, + online_order_url: Optional[str] = None, + wait_times: Optional[list[WaitTimesDay]] = None, + alerts: Optional[list[EateryAlert]] = None, ): self.id = id self.name = name @@ -87,18 +86,18 @@ def __init__( self.alerts = alerts def events( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None, + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None, ): return filter_range(self.known_events, tzinfo, start, end) def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None + self, + tzinfo: Optional[pytz.timezone] = None, + start: Optional[date] = None, + end: Optional[date] = None, ): eatery = { "id": None if self.id is None else self.id.value, @@ -106,7 +105,8 @@ def to_json( "image_url": self.image_url, "menu_summary": self.menu_summary, "campus_area": self.campus_area, - "events": None if self.known_events is None + "events": None + if self.known_events is None else [event.to_json() for event in self.events(tzinfo, start, end)], "latitude": self.latitude, "longitude": self.longitude, @@ -115,20 +115,27 @@ def to_json( "payment_accepts_meal_swipes": self.payment_accepts_meal_swipes, "location": self.location, "online_order_url": self.online_order_url, - "wait_times": None if self.wait_times is None else [wait_time.to_json() for wait_time in self.wait_times], - "alerts": None if self.alerts is None else [alert.to_json() for alert in self.alerts] + "wait_times": None + if self.wait_times is None + else [wait_time.to_json() for wait_time in self.wait_times], + "alerts": None + if self.alerts is None + else [alert.to_json() for alert in self.alerts], } return eatery @staticmethod def from_json(eatery_json): return Eatery( - id=None if "id" not in eatery_json or eatery_json["id"] is None else EateryID(eatery_json["id"]), + id=None + if "id" not in eatery_json or eatery_json["id"] is None + else EateryID(eatery_json["id"]), name=eatery_json.get("name"), image_url=eatery_json.get("image_url"), menu_summary=eatery_json.get("menu_summary"), campus_area=eatery_json.get("campus_area"), - events=None if "events" not in eatery_json or eatery_json["events"] is None + events=None + if "events" not in eatery_json or eatery_json["events"] is None else [Event.from_json(event) for event in eatery_json["events"]], latitude=eatery_json.get("latitude"), longitude=eatery_json.get("longitude"), @@ -137,10 +144,15 @@ def from_json(eatery_json): payment_accepts_meal_swipes=eatery_json.get("payment_accepts_meal_swipes"), location=eatery_json.get("location"), online_order_url=eatery_json.get("online_order_url"), - wait_times=None if "wait_times" not in eatery_json or eatery_json["wait_times"] is None - else [WaitTimesDay.from_json(day_wait_time) for day_wait_time in eatery_json["wait_times"]], - alerts=None if "alerts" not in eatery_json or eatery_json["alerts"] is None - else [EateryAlert.from_json(alert) for alert in eatery_json["alerts"]] + wait_times=None + if "wait_times" not in eatery_json or eatery_json["wait_times"] is None + else [ + WaitTimesDay.from_json(day_wait_time) + for day_wait_time in eatery_json["wait_times"] + ], + alerts=None + if "alerts" not in eatery_json or eatery_json["alerts"] is None + else [EateryAlert.from_json(alert) for alert in eatery_json["alerts"]], ) def clone(self): diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index f11ca9a..e64c5a9 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -39,7 +39,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "api", - "rest_framework" + "rest_framework", ] MIDDLEWARE = [ @@ -76,12 +76,26 @@ # Database # https://docs.djangoproject.com/en/4.0/ref/settings/#databases -DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", +IS_LOCAL = os.getenv("LOCAL") + +if IS_LOCAL: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } + } +else: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": os.getenv("POSTGRES_NAME"), + "USER": os.getenv("POSTGRES_USER"), + "PASSWORD": os.getenv("POSTGRES_PASSWORD"), + "HOST": os.getenv("POSTGRES_HOST"), + "PORT": os.getenv("POSTGRES_PORT"), + } } -} # Password validation From 74d71cb339d5eb1b53f12823546cf77b68b7a090 Mon Sep 17 00:00:00 2001 From: Marya Kim Date: Sat, 12 Mar 2022 11:34:58 -0500 Subject: [PATCH 068/305] Change handling of local env in settings.py --- .gitignore | 1 + src/eatery_blue_backend/settings.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 89ace21..54927b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Security .env eatery-dev.pem +.envrc # Hardcoded stuff db_snapshots/ diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index e64c5a9..b6edd5b 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -78,14 +78,14 @@ IS_LOCAL = os.getenv("LOCAL") -if IS_LOCAL: +if IS_LOCAL == "True": DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", } } -else: +elif IS_LOCAL == "False": DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql_psycopg2", From 87774627e912a34cc8fc7543e58211b34884b7a4 Mon Sep 17 00:00:00 2001 From: Marya Kim Date: Wed, 16 Mar 2022 18:08:45 -0400 Subject: [PATCH 069/305] Add null eatery --- src/api/datatype/Eatery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/datatype/Eatery.py b/src/api/datatype/Eatery.py index d21e536..d2a827a 100644 --- a/src/api/datatype/Eatery.py +++ b/src/api/datatype/Eatery.py @@ -48,6 +48,7 @@ class EateryID(Enum): LOUIES = 37 ANABELS_GROCERY = 38 MORRISON_DINING = 39 + NULL_EATERY = 41 class Eatery: From a727764c3f685309acf429a3a5f66ff9a118ac31 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Thu, 24 Mar 2022 16:37:10 -0400 Subject: [PATCH 070/305] Implement new menu DB schema and menu fetch calculation --- reset_db.sh | 4 ++ .../dfg/nodes/schedule/DayOfWeekSchedule.py | 45 ++++++++++---- ...ayofweekeventschedule_end_time_and_more.py | 50 ++++++++++++++++ ...03_alter_dayofweekeventschedule_offsets.py | 20 +++++++ ...fsets_dayofweekeventschedule_offset_lst.py | 18 ++++++ ...emove_dayofweekeventschedule_offset_lst.py | 17 ++++++ .../0006_dayofweekeventschedule_offset_lst.py | 21 +++++++ src/api/models/EventScheduleModel.py | 58 +++++++++++-------- src/temp_data.json | 0 9 files changed, 196 insertions(+), 37 deletions(-) create mode 100644 reset_db.sh create mode 100644 src/api/migrations/0002_rename_end_dayofweekeventschedule_end_time_and_more.py create mode 100644 src/api/migrations/0003_alter_dayofweekeventschedule_offsets.py create mode 100644 src/api/migrations/0004_rename_offsets_dayofweekeventschedule_offset_lst.py create mode 100644 src/api/migrations/0005_remove_dayofweekeventschedule_offset_lst.py create mode 100644 src/api/migrations/0006_dayofweekeventschedule_offset_lst.py create mode 100644 src/temp_data.json diff --git a/reset_db.sh b/reset_db.sh new file mode 100644 index 0000000..3490cf3 --- /dev/null +++ b/reset_db.sh @@ -0,0 +1,4 @@ +source env/bin/activate +source .env +python3 src/manage.py makemigrations +python3 src/manage.py migrate \ No newline at end of file diff --git a/src/api/dfg/nodes/schedule/DayOfWeekSchedule.py b/src/api/dfg/nodes/schedule/DayOfWeekSchedule.py index a469988..7b195ce 100644 --- a/src/api/dfg/nodes/schedule/DayOfWeekSchedule.py +++ b/src/api/dfg/nodes/schedule/DayOfWeekSchedule.py @@ -6,29 +6,50 @@ from api.models import DayOfWeekEventSchedule from api.util.time import combined_timestamp -class DayOfWeekSchedule(DfgNode): +class DayOfWeekSchedule(DfgNode): def __init__(self, eatery_id: EateryID, cache): self.eatery_id = eatery_id self.cache = cache def __call__(self, *args, **kwargs) -> list[Eatery]: if "day_of_week_schedules" not in self.cache: - self.cache["day_of_week_schedules"] = DayOfWeekEventSchedule.objects.all().values() - schedules = [sched for sched in self.cache["day_of_week_schedules"] if EateryID(sched["eatery_id"]) == self.eatery_id] + self.cache[ + "day_of_week_schedules" + ] = DayOfWeekEventSchedule.objects.all().values() + dow_schedules = [ + sched + for sched in self.cache["day_of_week_schedules"] + if EateryID(sched["eatery_id"]) == self.eatery_id + ] events = [] date = kwargs.get("start") while date <= kwargs.get("end"): - day_schedule = [sched for sched in schedules if sched["day_of_week"] == date.strftime("%A")] + day_schedule = [ + sched + for sched in dow_schedules + if str((date - sched["start_date"]).days % sched["repeat_interval"]) + in sched["offset_lst"].split(",") + ] for sched in day_schedule: - events.append(Event( - description=sched["event_description"], - canonical_date=date, - start_timestamp=combined_timestamp(date=date, time=sched["start"], tzinfo=kwargs.get("tzinfo")), - end_timestamp=combined_timestamp(date=date, time=sched["end"], tzinfo=kwargs.get("tzinfo")), - menu=self.cache["menus"][self.eatery_id][sched["menu_id"]] - )) - date += timedelta(days = 1) + events.append( + Event( + description=sched["event_description"], + canonical_date=date, + start_timestamp=combined_timestamp( + date=date, + time=sched["start_time"], + tzinfo=kwargs.get("tzinfo"), + ), + end_timestamp=combined_timestamp( + date=date, + time=sched["end_time"], + tzinfo=kwargs.get("tzinfo"), + ), + menu=self.cache["menus"][self.eatery_id][sched["menu_id"]], + ) + ) + date += timedelta(days=1) return events def description(self): diff --git a/src/api/migrations/0002_rename_end_dayofweekeventschedule_end_time_and_more.py b/src/api/migrations/0002_rename_end_dayofweekeventschedule_end_time_and_more.py new file mode 100644 index 0000000..954542c --- /dev/null +++ b/src/api/migrations/0002_rename_end_dayofweekeventschedule_end_time_and_more.py @@ -0,0 +1,50 @@ +# Generated by Django 4.0 on 2022-03-24 20:16 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='dayofweekeventschedule', + old_name='end', + new_name='end_time', + ), + migrations.RenameField( + model_name='dayofweekeventschedule', + old_name='start', + new_name='start_time', + ), + migrations.AlterUniqueTogether( + name='dayofweekeventschedule', + unique_together={('eatery', 'event_description')}, + ), + migrations.AddField( + model_name='dayofweekeventschedule', + name='offsets', + field=models.JSONField(default=''), + preserve_default=False, + ), + migrations.AddField( + model_name='dayofweekeventschedule', + name='repeat_interval', + field=models.IntegerField(default=7), + preserve_default=False, + ), + migrations.AddField( + model_name='dayofweekeventschedule', + name='start_date', + field=models.DateField(default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.RemoveField( + model_name='dayofweekeventschedule', + name='day_of_week', + ), + ] diff --git a/src/api/migrations/0003_alter_dayofweekeventschedule_offsets.py b/src/api/migrations/0003_alter_dayofweekeventschedule_offsets.py new file mode 100644 index 0000000..f82fcd8 --- /dev/null +++ b/src/api/migrations/0003_alter_dayofweekeventschedule_offsets.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0 on 2022-03-24 20:22 + +import django.core.validators +from django.db import migrations, models +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0002_rename_end_dayofweekeventschedule_end_time_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='dayofweekeventschedule', + name='offsets', + field=models.CharField(max_length=100, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')]), + ), + ] diff --git a/src/api/migrations/0004_rename_offsets_dayofweekeventschedule_offset_lst.py b/src/api/migrations/0004_rename_offsets_dayofweekeventschedule_offset_lst.py new file mode 100644 index 0000000..ba9cb5d --- /dev/null +++ b/src/api/migrations/0004_rename_offsets_dayofweekeventschedule_offset_lst.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2022-03-24 20:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0003_alter_dayofweekeventschedule_offsets'), + ] + + operations = [ + migrations.RenameField( + model_name='dayofweekeventschedule', + old_name='offsets', + new_name='offset_lst', + ), + ] diff --git a/src/api/migrations/0005_remove_dayofweekeventschedule_offset_lst.py b/src/api/migrations/0005_remove_dayofweekeventschedule_offset_lst.py new file mode 100644 index 0000000..b4ae667 --- /dev/null +++ b/src/api/migrations/0005_remove_dayofweekeventschedule_offset_lst.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0 on 2022-03-24 20:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0004_rename_offsets_dayofweekeventschedule_offset_lst'), + ] + + operations = [ + migrations.RemoveField( + model_name='dayofweekeventschedule', + name='offset_lst', + ), + ] diff --git a/src/api/migrations/0006_dayofweekeventschedule_offset_lst.py b/src/api/migrations/0006_dayofweekeventschedule_offset_lst.py new file mode 100644 index 0000000..83311f4 --- /dev/null +++ b/src/api/migrations/0006_dayofweekeventschedule_offset_lst.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0 on 2022-03-24 20:28 + +import django.core.validators +from django.db import migrations, models +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0005_remove_dayofweekeventschedule_offset_lst'), + ] + + operations = [ + migrations.AddField( + model_name='dayofweekeventschedule', + name='offset_lst', + field=models.CharField(default='0,2', max_length=100, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')]), + preserve_default=False, + ), + ] diff --git a/src/api/models/EventScheduleModel.py b/src/api/models/EventScheduleModel.py index d85f82c..c024f97 100644 --- a/src/api/models/EventScheduleModel.py +++ b/src/api/models/EventScheduleModel.py @@ -1,47 +1,55 @@ from django.db import models +from django.core.validators import validate_comma_separated_integer_list from api.models.EateryModel import EateryStore from api.models.MenuModel import MenuStore + class EventDescription(models.TextChoices): - BREAKFAST = 'Breakfast' - BRUNCH = 'Brunch' - LUNCH = 'Lunch' - DINNER = 'Dinner' - GENERAL = 'General' + BREAKFAST = "Breakfast" + BRUNCH = "Brunch" + LUNCH = "Lunch" + DINNER = "Dinner" + GENERAL = "General" + class EventSchedule(models.Model): id = models.IntegerField(primary_key=True) eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + class Meta: - abstract=True + abstract = True + class DayOfWeekEventSchedule(EventSchedule): - - class DayOfTheWeek(models.TextChoices): - MONDAY = 'Monday' - TUESDAY = 'Tuesday' - WEDNESDAY = 'Wednesday' - THURSDAY = 'Thursday' - FRIDAY = 'Friday' - SATURDAY = 'Saturday' - SUNDAY = 'Sunday' - - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) - menu= models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - day_of_week = models.CharField(choices = DayOfTheWeek.choices, max_length=10) - start = models.TimeField() - end = models.TimeField() + + event_description = models.CharField( + choices=EventDescription.choices, max_length=10 + ) + menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) + start_date = models.DateField() + start_time = models.TimeField() + end_time = models.TimeField() + repeat_interval = models.IntegerField() + offset_lst = models.CharField( + validators=[validate_comma_separated_integer_list], max_length=100 + ) + class Meta: - unique_together = ('eatery', 'day_of_week', 'event_description') - + unique_together = ("eatery", "event_description") + class DateEventSchedule(EventSchedule): - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + event_description = models.CharField( + choices=EventDescription.choices, max_length=10 + ) menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) canonical_date = models.DateField() start_timestamp = models.IntegerField() end_timestamp = models.IntegerField() + class ClosedEventSchedule(EventSchedule): - event_description = models.CharField(choices=EventDescription.choices, max_length = 10) + event_description = models.CharField( + choices=EventDescription.choices, max_length=10 + ) canonical_date = models.DateField() diff --git a/src/temp_data.json b/src/temp_data.json new file mode 100644 index 0000000..e69de29 From f3bb4a60e3e04c3de88453dcb9c48bc5b9a58ee9 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Fri, 25 Mar 2022 14:21:14 -0400 Subject: [PATCH 071/305] Rename DayOfWeek model and DfgNode to RepeatingSchedule --- src/api/admin.py | 5 +- src/api/dfg/nodes/macros/EateryEvents.py | 14 ++-- ...OfWeekSchedule.py => RepeatingSchedule.py} | 12 +-- .../management/commands/export_db_snapshot.py | 76 +++++++++++++++---- .../management/commands/ingest_db_snapshot.py | 59 +++++++++----- ...eekeventschedule_repeatingeventschedule.py | 17 +++++ src/api/models/EventScheduleModel.py | 2 +- src/api/models/__init__.py | 19 ++++- src/api/serializers.py | 28 ++++--- 9 files changed, 166 insertions(+), 66 deletions(-) rename src/api/dfg/nodes/schedule/{DayOfWeekSchedule.py => RepeatingSchedule.py} (87%) create mode 100644 src/api/migrations/0007_rename_dayofweekeventschedule_repeatingeventschedule.py diff --git a/src/api/admin.py b/src/api/admin.py index 70c50e0..f3216d0 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin import api.models as models + # Register your models here. admin.site.register(models.EateryStore) @@ -10,8 +11,8 @@ admin.site.register(models.AlertStore) admin.site.register(models.CategoryStore) admin.site.register(models.CategoryItemAssociation) -admin.site.register(models.DayOfWeekEventSchedule) +admin.site.register(models.RepeatingEventSchedule) admin.site.register(models.DateEventSchedule) admin.site.register(models.ClosedEventSchedule) admin.site.register(models.TransactionHistoryStore) -admin.site.register(models.ReportStore) \ No newline at end of file +admin.site.register(models.ReportStore) diff --git a/src/api/dfg/nodes/macros/EateryEvents.py b/src/api/dfg/nodes/macros/EateryEvents.py index 460403d..00a2530 100644 --- a/src/api/dfg/nodes/macros/EateryEvents.py +++ b/src/api/dfg/nodes/macros/EateryEvents.py @@ -1,7 +1,7 @@ from api.dfg.nodes.DfgNode import DfgNode from api.dfg.nodes.schedule.ClosedSchedule import ClosedSchedule -from api.dfg.nodes.schedule.DayOfWeekSchedule import DayOfWeekSchedule +from api.dfg.nodes.schedule.RepeatingSchedule import RepeatingSchedule from api.dfg.nodes.schedule.DateSchedule import DateSchedule from api.dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents @@ -20,13 +20,13 @@ def __init__(self, eatery_id: EateryID, cache): LeftMergeEvents( DateSchedule(eatery_id, cache), LeftMergeEvents( - DayOfWeekSchedule(eatery_id, cache), - CornellDiningEvents(eatery_id, cache) - ) + RepeatingSchedule(eatery_id, cache), + CornellDiningEvents(eatery_id, cache), + ), ), - cache + cache, ), - cache + cache, ) def children(self): @@ -36,4 +36,4 @@ def __call__(self, *args, **kwargs): return self.macro(*args, **kwargs) def description(self): - return "EateryEvents" \ No newline at end of file + return "EateryEvents" diff --git a/src/api/dfg/nodes/schedule/DayOfWeekSchedule.py b/src/api/dfg/nodes/schedule/RepeatingSchedule.py similarity index 87% rename from src/api/dfg/nodes/schedule/DayOfWeekSchedule.py rename to src/api/dfg/nodes/schedule/RepeatingSchedule.py index 7b195ce..b60a618 100644 --- a/src/api/dfg/nodes/schedule/DayOfWeekSchedule.py +++ b/src/api/dfg/nodes/schedule/RepeatingSchedule.py @@ -3,11 +3,11 @@ from api.datatype.Eatery import Eatery, EateryID from api.datatype.Event import Event from api.dfg.nodes.DfgNode import DfgNode -from api.models import DayOfWeekEventSchedule +from api.models import RepeatingEventSchedule from api.util.time import combined_timestamp -class DayOfWeekSchedule(DfgNode): +class RepeatingSchedule(DfgNode): def __init__(self, eatery_id: EateryID, cache): self.eatery_id = eatery_id self.cache = cache @@ -16,8 +16,8 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: if "day_of_week_schedules" not in self.cache: self.cache[ "day_of_week_schedules" - ] = DayOfWeekEventSchedule.objects.all().values() - dow_schedules = [ + ] = RepeatingEventSchedule.objects.all().values() + repeating_schedules = [ sched for sched in self.cache["day_of_week_schedules"] if EateryID(sched["eatery_id"]) == self.eatery_id @@ -27,7 +27,7 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: while date <= kwargs.get("end"): day_schedule = [ sched - for sched in dow_schedules + for sched in repeating_schedules if str((date - sched["start_date"]).days % sched["repeat_interval"]) in sched["offset_lst"].split(",") ] @@ -53,4 +53,4 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: return events def description(self): - return "DayOfWeekSchedule" + return "RepeatingSchedule" diff --git a/src/api/management/commands/export_db_snapshot.py b/src/api/management/commands/export_db_snapshot.py index d2bc6a7..eaabfd2 100644 --- a/src/api/management/commands/export_db_snapshot.py +++ b/src/api/management/commands/export_db_snapshot.py @@ -1,4 +1,3 @@ - from django.core.management.base import BaseCommand from datetime import datetime @@ -11,10 +10,13 @@ import api.models as models import api.serializers as serializers + class Command(BaseCommand): - help = 'Saves the current state of the database' + help = "Saves the current state of the database" - def write_to_file(self, serializer, db_objects, folder_path, file_name_enum: SnapshotFileName): + def write_to_file( + self, serializer, db_objects, folder_path, file_name_enum: SnapshotFileName + ): file_path = f"{folder_path}/{file_name_enum.value}" serialized_lst = serializer(db_objects, many=True) with open(file_path, "w") as file: @@ -26,27 +28,64 @@ def handle(self, *args, **options): time = datetime.now(tzinfo).strftime("%Y-%m-%d %H:%M:%S") folder_path = f"db_snapshots/{time}" Path(folder_path).mkdir(parents=True, exist_ok=True) - - eateries = models.EateryStore.objects.all() - self.write_to_file(serializers.EateryStoreSerializer, eateries, folder_path, SnapshotFileName.EATERY_STORE) - alerts = models.AlertStore.objects.filter(end_timestamp__gte=datetime.now().timestamp()) - self.write_to_file(serializers.AlertStoreSerializer, alerts, folder_path, SnapshotFileName.ALERT_STORE) + eateries = models.EateryStore.objects.all() + self.write_to_file( + serializers.EateryStoreSerializer, + eateries, + folder_path, + SnapshotFileName.EATERY_STORE, + ) + + alerts = models.AlertStore.objects.filter( + end_timestamp__gte=datetime.now().timestamp() + ) + self.write_to_file( + serializers.AlertStoreSerializer, + alerts, + folder_path, + SnapshotFileName.ALERT_STORE, + ) menus = models.MenuStore.objects.all() - self.write_to_file(serializers.MenuStoreSerializer, menus, folder_path, SnapshotFileName.MENU_STORE) + self.write_to_file( + serializers.MenuStoreSerializer, + menus, + folder_path, + SnapshotFileName.MENU_STORE, + ) categories = models.CategoryStore.objects.all() - self.write_to_file(serializers.CategoryStoreSerializer, categories, folder_path, SnapshotFileName.CATEGORY_STORE) + self.write_to_file( + serializers.CategoryStoreSerializer, + categories, + folder_path, + SnapshotFileName.CATEGORY_STORE, + ) items = models.ItemStore.objects.all() - self.write_to_file(serializers.ItemStoreSerializer, items, folder_path, SnapshotFileName.ITEM_STORE) + self.write_to_file( + serializers.ItemStoreSerializer, + items, + folder_path, + SnapshotFileName.ITEM_STORE, + ) subitems = models.SubItemStore.objects.all() - self.write_to_file(serializers.SubItemStoreSerializer, subitems, folder_path, SnapshotFileName.SUBITEM_STORE) + self.write_to_file( + serializers.SubItemStoreSerializer, + subitems, + folder_path, + SnapshotFileName.SUBITEM_STORE, + ) category_item_associations = models.CategoryItemAssociation.objects.all() - self.write_to_file(serializers.CategoryItemAssociationSerializer, category_item_associations, folder_path, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) + self.write_to_file( + serializers.CategoryItemAssociationSerializer, + category_item_associations, + folder_path, + SnapshotFileName.CATEGORY_ITEM_ASSOCIATION, + ) # date_event_schedules = models.DateEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) # self.write_to_file(date_event_schedules, f"{folder_path}/{SnapshotFileName.DATE_EVENT_SCHEDULE}") @@ -54,9 +93,14 @@ def handle(self, *args, **options): # closed_event_schedules = models.ClosedEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) # self.write_to_file(closed_event_schedules, f"{folder_path}/{SnapshotFileName.CLOSED_EVENT_SCHEDULE}") - day_of_week_event_schedules = models.DayOfWeekEventSchedule.objects.all() - self.write_to_file(serializers.DayOfWeekEventScheduleSerializer, day_of_week_event_schedules, folder_path, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) + day_of_week_event_schedules = models.RepeatingEventSchedule.objects.all() + self.write_to_file( + serializers.RepeatingEventScheduleSerializer, + day_of_week_event_schedules, + folder_path, + SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE, + ) # # TODO: Need to filter here for only the valid schedules # event_schedules = models.EventSchedule.objects.all() - # self.write_to_file(event_schedules, f"{folder_path}/{SnapshotFileName.EVENT_SCHEDULE}") \ No newline at end of file + # self.write_to_file(event_schedules, f"{folder_path}/{SnapshotFileName.EVENT_SCHEDULE}") diff --git a/src/api/management/commands/ingest_db_snapshot.py b/src/api/management/commands/ingest_db_snapshot.py index 40b5c72..adfe06c 100644 --- a/src/api/management/commands/ingest_db_snapshot.py +++ b/src/api/management/commands/ingest_db_snapshot.py @@ -1,4 +1,3 @@ - from django.core.management.base import BaseCommand from api.util.constants import SnapshotFileName import api.serializers as serializers @@ -6,17 +5,19 @@ class Command(BaseCommand): - help = 'Overrides current state of the db with a db snapshot. Takes --input argument' + help = ( + "Overrides current state of the db with a db snapshot. Takes --input argument" + ) def add_arguments(self, parser): - parser.add_argument('--input', type=str) + parser.add_argument("--input", type=str) # Only writes data if the table has been flushed def ingest_data(self, serializer, folder_path: str, file_name: SnapshotFileName): with open(f"{folder_path}/{file_name.value}", "r") as file: json_objs = [] for line in file: - if (len(line) > 2): + if len(line) > 2: json_objs.append(json.loads(line)) serialized_objs = serializer(data=json_objs, many=True) serialized_objs.is_valid() @@ -24,19 +25,37 @@ def ingest_data(self, serializer, folder_path: str, file_name: SnapshotFileName) def handle(self, *args, **options): folder_path = options["input"] - self.ingest_data(serializers.EateryStoreSerializer, - folder_path, SnapshotFileName.EATERY_STORE) - self.ingest_data(serializers.AlertStoreSerializer, - folder_path, SnapshotFileName.ALERT_STORE) - self.ingest_data(serializers.MenuStoreSerializer, - folder_path, SnapshotFileName.MENU_STORE) - self.ingest_data(serializers.CategoryStoreSerializer, - folder_path, SnapshotFileName.CATEGORY_STORE) - self.ingest_data(serializers.ItemStoreSerializer, - folder_path, SnapshotFileName.ITEM_STORE) - self.ingest_data(serializers.SubItemStoreSerializer, - folder_path, SnapshotFileName.SUBITEM_STORE) - self.ingest_data(serializers.CategoryItemAssociationSerializer, - folder_path, SnapshotFileName.CATEGORY_ITEM_ASSOCIATION) - self.ingest_data(serializers.DayOfWeekEventScheduleSerializer, - folder_path, SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE) + self.ingest_data( + serializers.EateryStoreSerializer, + folder_path, + SnapshotFileName.EATERY_STORE, + ) + self.ingest_data( + serializers.AlertStoreSerializer, folder_path, SnapshotFileName.ALERT_STORE + ) + self.ingest_data( + serializers.MenuStoreSerializer, folder_path, SnapshotFileName.MENU_STORE + ) + self.ingest_data( + serializers.CategoryStoreSerializer, + folder_path, + SnapshotFileName.CATEGORY_STORE, + ) + self.ingest_data( + serializers.ItemStoreSerializer, folder_path, SnapshotFileName.ITEM_STORE + ) + self.ingest_data( + serializers.SubItemStoreSerializer, + folder_path, + SnapshotFileName.SUBITEM_STORE, + ) + self.ingest_data( + serializers.CategoryItemAssociationSerializer, + folder_path, + SnapshotFileName.CATEGORY_ITEM_ASSOCIATION, + ) + self.ingest_data( + serializers.RepeatingEventScheduleSerializer, + folder_path, + SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE, + ) diff --git a/src/api/migrations/0007_rename_dayofweekeventschedule_repeatingeventschedule.py b/src/api/migrations/0007_rename_dayofweekeventschedule_repeatingeventschedule.py new file mode 100644 index 0000000..f11c20c --- /dev/null +++ b/src/api/migrations/0007_rename_dayofweekeventschedule_repeatingeventschedule.py @@ -0,0 +1,17 @@ +# Generated by Django 4.0 on 2022-03-25 18:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0006_dayofweekeventschedule_offset_lst'), + ] + + operations = [ + migrations.RenameModel( + old_name='DayOfWeekEventSchedule', + new_name='RepeatingEventSchedule', + ), + ] diff --git a/src/api/models/EventScheduleModel.py b/src/api/models/EventScheduleModel.py index c024f97..f4325eb 100644 --- a/src/api/models/EventScheduleModel.py +++ b/src/api/models/EventScheduleModel.py @@ -20,7 +20,7 @@ class Meta: abstract = True -class DayOfWeekEventSchedule(EventSchedule): +class RepeatingEventSchedule(EventSchedule): event_description = models.CharField( choices=EventDescription.choices, max_length=10 diff --git a/src/api/models/__init__.py b/src/api/models/__init__.py index 2ecaea6..7a9fc78 100644 --- a/src/api/models/__init__.py +++ b/src/api/models/__init__.py @@ -1,9 +1,20 @@ -# Need to expose models to django. +# Need to expose models to django. # In this app, models should be imported directly from api.models, not from api.models.package from .AlertModel import AlertStore from .EateryModel import EateryStore -from .EventScheduleModel import EventSchedule, ClosedEventSchedule, DateEventSchedule, DayOfWeekEventSchedule -from .MenuModel import MenuStore, CategoryStore, ItemStore, SubItemStore, CategoryItemAssociation +from .EventScheduleModel import ( + EventSchedule, + ClosedEventSchedule, + DateEventSchedule, + RepeatingEventSchedule, +) +from .MenuModel import ( + MenuStore, + CategoryStore, + ItemStore, + SubItemStore, + CategoryItemAssociation, +) from .ReportModel import ReportStore -from .TransactionModel import TransactionHistoryStore \ No newline at end of file +from .TransactionModel import TransactionHistoryStore diff --git a/src/api/serializers.py b/src/api/serializers.py index c0c8fbe..db9698e 100644 --- a/src/api/serializers.py +++ b/src/api/serializers.py @@ -2,42 +2,50 @@ import api.models as models + class EateryStoreSerializer(serializers.ModelSerializer): class Meta: model = models.EateryStore - fields = '__all__' + fields = "__all__" + class AlertStoreSerializer(serializers.ModelSerializer): class Meta: model = models.AlertStore - fields = '__all__' + fields = "__all__" + class MenuStoreSerializer(serializers.ModelSerializer): class Meta: model = models.MenuStore - fields = '__all__' + fields = "__all__" + class ItemStoreSerializer(serializers.ModelSerializer): class Meta: model = models.ItemStore - fields = '__all__' + fields = "__all__" + class SubItemStoreSerializer(serializers.ModelSerializer): class Meta: model = models.SubItemStore - fields = '__all__' + fields = "__all__" + class CategoryStoreSerializer(serializers.ModelSerializer): class Meta: model = models.CategoryStore - fields = '__all__' + fields = "__all__" + class CategoryItemAssociationSerializer(serializers.ModelSerializer): class Meta: model = models.CategoryItemAssociation - fields = '__all__' + fields = "__all__" + -class DayOfWeekEventScheduleSerializer(serializers.ModelSerializer): +class RepeatingEventScheduleSerializer(serializers.ModelSerializer): class Meta: - model = models.DayOfWeekEventSchedule - fields = '__all__' \ No newline at end of file + model = models.RepeatingEventSchedule + fields = "__all__" From f47202177ef8c64afb8e696231d8f3a3449c12de Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sat, 26 Mar 2022 22:45:18 -0400 Subject: [PATCH 072/305] Remove empty temp_data --- src/temp_data.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/temp_data.json diff --git a/src/temp_data.json b/src/temp_data.json deleted file mode 100644 index e69de29..0000000 From 0115d1aa7b8ad059c187e03b3b38ebc80007d234 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sat, 26 Mar 2022 22:57:04 -0400 Subject: [PATCH 073/305] Added PR Template and CODEOWNERS --- .github/CODEOWNERS | 1 + .github/pull_request_template.md | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .github/CODEOWNERS create mode 100644 .github/pull_request_template.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..5f3eb94 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @Archit404Error @connorreinhold @chalo2000 @markim21 \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..98500dd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,48 @@ + + + + + +## Overview + + + + + +## Changes Made + + + + + +## Test Coverage + + + + + +## Next Steps (delete if not applicable) + + + + + +## Related PRs or Issues (delete if not applicable) + + + + + +## Screenshots (delete if not applicable) + + + +
+ + Screen Shot Name + + + + + +
From 238f56bb7a95e03159c0eacf593962ce08e2d2d9 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Tue, 12 Apr 2022 22:48:17 -0400 Subject: [PATCH 074/305] Migration to add ScheduleException model --- src/api/admin.py | 3 +- src/api/dfg/main.py | 38 +++++++--------- src/api/dfg/nodes/macros/EateryEvents.py | 12 +++-- .../dfg/nodes/schedule/CacheMenuInjection.py | 45 ++++++++++--------- src/api/dfg/nodes/schedule/ClosedSchedule.py | 7 ++- .../management/commands/export_db_snapshot.py | 22 ++++----- ...emove_dateeventschedule_eatery_and_more.py | 38 ++++++++++++++++ src/api/models/EventScheduleModel.py | 29 ++++++------ src/api/models/MenuModel.py | 23 ++++++---- src/api/models/__init__.py | 3 +- src/api/serializers.py | 5 +++ src/api/util/constants.py | 12 +++-- 12 files changed, 145 insertions(+), 92 deletions(-) create mode 100644 src/api/migrations/0008_scheduleexception_remove_dateeventschedule_eatery_and_more.py diff --git a/src/api/admin.py b/src/api/admin.py index f3216d0..499455c 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -12,7 +12,6 @@ admin.site.register(models.CategoryStore) admin.site.register(models.CategoryItemAssociation) admin.site.register(models.RepeatingEventSchedule) -admin.site.register(models.DateEventSchedule) -admin.site.register(models.ClosedEventSchedule) +admin.site.register(models.ScheduleException) admin.site.register(models.TransactionHistoryStore) admin.site.register(models.ReportStore) diff --git a/src/api/dfg/main.py b/src/api/dfg/main.py index 8de2b9f..2a12997 100644 --- a/src/api/dfg/main.py +++ b/src/api/dfg/main.py @@ -1,18 +1,15 @@ from api.dfg.nodes.CornellDiningNow import CornellDiningNow -from api.dfg.nodes.EateryStubs import EateryStubs from api.dfg.nodes.EateriesFromDB import EateriesFromDB +from api.dfg.nodes.EateryStubs import EateryStubs from api.dfg.nodes.macros.EateryEvents import EateryEvents - -from api.dfg.nodes.system.DictResponseWrapper import DictResponseWrapper +from api.dfg.nodes.macros.LeftMergeEateries import LeftMergeEateries from api.dfg.nodes.system.ConvertToJson import ConvertToJson +from api.dfg.nodes.system.DictResponseWrapper import DictResponseWrapper from api.dfg.nodes.system.EateryGenerator import EateryGenerator from api.dfg.nodes.system.InMemoryCache import InMemoryCache from api.dfg.nodes.system.Mapping import Mapping - -from api.dfg.nodes.macros.LeftMergeEateries import LeftMergeEateries - -from api.dfg.nodes.wait_times.WaitTimes import WaitTimes from api.dfg.nodes.wait_times.WaitTimeFilter import WaitTimeFilter +from api.dfg.nodes.wait_times.WaitTimes import WaitTimes main_dfg = DictResponseWrapper( ConvertToJson( @@ -21,30 +18,27 @@ LeftMergeEateries( Mapping( child=EateryStubs(), - fn = lambda eatery, cache: EateryGenerator( + fn=lambda eatery, cache: EateryGenerator( eatery_id=eatery.id, - wait_times_dfg=WaitTimes(eatery.id, cache) - ) + wait_times_dfg=WaitTimes(eatery.id, cache), + ), ), LeftMergeEateries( Mapping( - child = EateryStubs(), - fn = lambda eatery, cache: EateryGenerator( + child=EateryStubs(), + fn=lambda eatery, cache: EateryGenerator( eatery_id=eatery.id, - events_dfg=EateryEvents(eatery.id, cache) - ) + events_dfg=EateryEvents(eatery.id, cache), + ), ), LeftMergeEateries( EateriesFromDB(), - LeftMergeEateries( - CornellDiningNow(), - EateryStubs() - ) - ) - ) + LeftMergeEateries(CornellDiningNow(), EateryStubs()), + ), + ), ) ) ) ), - re_raise_exceptions=True -) \ No newline at end of file + re_raise_exceptions=True, +) diff --git a/src/api/dfg/nodes/macros/EateryEvents.py b/src/api/dfg/nodes/macros/EateryEvents.py index 00a2530..ac44530 100644 --- a/src/api/dfg/nodes/macros/EateryEvents.py +++ b/src/api/dfg/nodes/macros/EateryEvents.py @@ -1,14 +1,12 @@ +from api.datatype.Eatery import EateryID from api.dfg.nodes.DfgNode import DfgNode - +from api.dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents +from api.dfg.nodes.schedule.CacheMenuInjection import CacheMenuInjection from api.dfg.nodes.schedule.ClosedSchedule import ClosedSchedule -from api.dfg.nodes.schedule.RepeatingSchedule import RepeatingSchedule -from api.dfg.nodes.schedule.DateSchedule import DateSchedule from api.dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents +from api.dfg.nodes.schedule.DateSchedule import DateSchedule +from api.dfg.nodes.schedule.RepeatingSchedule import RepeatingSchedule -from api.dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents - -from api.datatype.Eatery import EateryID -from api.dfg.nodes.schedule.CacheMenuInjection import CacheMenuInjection # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if # conflict) diff --git a/src/api/dfg/nodes/schedule/CacheMenuInjection.py b/src/api/dfg/nodes/schedule/CacheMenuInjection.py index 3ea7601..9351471 100644 --- a/src/api/dfg/nodes/schedule/CacheMenuInjection.py +++ b/src/api/dfg/nodes/schedule/CacheMenuInjection.py @@ -1,25 +1,26 @@ +from api.datatype.Eatery import Eatery, EateryID from api.datatype.Menu import Menu from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem from api.datatype.MenuItemSection import MenuItemSection from api.datatype.MenuSubItem import MenuSubItem -from api.datatype.Eatery import Eatery, EateryID from api.dfg.nodes.DfgNode import DfgNode from api.models import CategoryItemAssociation, SubItemStore -class CacheMenuInjection(DfgNode): +class CacheMenuInjection(DfgNode): def __init__(self, child: DfgNode, cache): self.cache = cache self.child = child def __call__(self, *args, **kwargs) -> list[Eatery]: if "menus" not in self.cache: - associations = CategoryItemAssociation.objects \ - .select_related("item") \ - .select_related("category") \ - .select_related("category__menu") \ - .all() + associations = ( + CategoryItemAssociation.objects.select_related("item") + .select_related("category") + .select_related("category__menu") + .all() + ) subitems = SubItemStore.objects.all() eatery_menus_categories_map = {} @@ -33,9 +34,9 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: item_subitem_map[item_id][item_subsection] = [] item_subitem_map[item_id][item_subsection].append( MenuSubItem( - name=subitem.name, - additional_price=subitem.additional_price, - total_price=subitem.total_price + name=subitem.name, + additional_price=subitem.additional_price, + total_price=subitem.total_price, ) ) @@ -53,14 +54,18 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: if association.item.id in item_subitem_map: item_sections = [] for section in item_subitem_map[association.item.id]: - item_sections.append(MenuItemSection(section, item_subitem_map[association.item.id][section])) + item_sections.append( + MenuItemSection( + section, item_subitem_map[association.item.id][section] + ) + ) eatery_menus_categories_map[eatery_id][menu_id][category].append( MenuItem( - name = association.item.name, - healthy = None, - base_price = association.item.base_price, - description = association.item.description, - sections = item_sections + name=association.item.name, + healthy=None, + base_price=association.item.base_price, + description=association.item.description, + sections=item_sections, ) ) eatery_menus_map = {} @@ -72,12 +77,12 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: categories.append( MenuCategory( category=category, - items = eatery_menus_categories_map[eatery_id][menu_id][category] + items=eatery_menus_categories_map[eatery_id][menu_id][ + category + ], ) ) - eatery_menus_map[eatery_id][menu_id] = Menu( - categories=categories - ) + eatery_menus_map[eatery_id][menu_id] = Menu(categories=categories) self.cache["menus"] = eatery_menus_map # self.cache["menus"] is a map of form {[eatery_id]: {[menu_id]: [Menu]}} diff --git a/src/api/dfg/nodes/schedule/ClosedSchedule.py b/src/api/dfg/nodes/schedule/ClosedSchedule.py index 52ca7d0..5536a3a 100644 --- a/src/api/dfg/nodes/schedule/ClosedSchedule.py +++ b/src/api/dfg/nodes/schedule/ClosedSchedule.py @@ -1,7 +1,12 @@ -from api.dfg.nodes.DfgNode import DfgNode from api.datatype.Eatery import Eatery, EateryID +from api.dfg.nodes.DfgNode import DfgNode + class ClosedSchedule(DfgNode): + """ + This DfgNode takes in a child consisting of a DfgNode and filters out all schedules from the node + if the eatery is closed on that day + """ def __init__(self, eatery_id: EateryID, child: DfgNode, cache): self.eatery_id = eatery_id diff --git a/src/api/management/commands/export_db_snapshot.py b/src/api/management/commands/export_db_snapshot.py index eaabfd2..beb5ca2 100644 --- a/src/api/management/commands/export_db_snapshot.py +++ b/src/api/management/commands/export_db_snapshot.py @@ -1,14 +1,12 @@ -from django.core.management.base import BaseCommand - +import json from datetime import datetime from pathlib import Path -import pytz -import json - -from api.util.constants import SnapshotFileName import api.models as models import api.serializers as serializers +import pytz +from api.util.constants import SnapshotFileName +from django.core.management.base import BaseCommand class Command(BaseCommand): @@ -87,11 +85,13 @@ def handle(self, *args, **options): SnapshotFileName.CATEGORY_ITEM_ASSOCIATION, ) - # date_event_schedules = models.DateEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) - # self.write_to_file(date_event_schedules, f"{folder_path}/{SnapshotFileName.DATE_EVENT_SCHEDULE}") - - # closed_event_schedules = models.ClosedEventSchedule.objects.filter(canonical_date_gte=datetime.now().date) - # self.write_to_file(closed_event_schedules, f"{folder_path}/{SnapshotFileName.CLOSED_EVENT_SCHEDULE}") + schedule_exceptions = models.ScheduleException.objects.all() + self.write_to_file( + serializers.ScheduleExceptionSerializer, + schedule_exceptions, + folder_path, + SnapshotFileName.SCHEDULE_EXCEPTION, + ) day_of_week_event_schedules = models.RepeatingEventSchedule.objects.all() self.write_to_file( diff --git a/src/api/migrations/0008_scheduleexception_remove_dateeventschedule_eatery_and_more.py b/src/api/migrations/0008_scheduleexception_remove_dateeventschedule_eatery_and_more.py new file mode 100644 index 0000000..0f2f17a --- /dev/null +++ b/src/api/migrations/0008_scheduleexception_remove_dateeventschedule_eatery_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.0 on 2022-04-13 02:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0007_rename_dayofweekeventschedule_repeatingeventschedule'), + ] + + operations = [ + migrations.CreateModel( + name='ScheduleException', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('date', models.DateField()), + ('start_time', models.DateField(blank=True, null=True)), + ('end_time', models.DateField(blank=True, null=True)), + ('parent', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.repeatingeventschedule')), + ], + ), + migrations.RemoveField( + model_name='dateeventschedule', + name='eatery', + ), + migrations.RemoveField( + model_name='dateeventschedule', + name='menu', + ), + migrations.DeleteModel( + name='ClosedEventSchedule', + ), + migrations.DeleteModel( + name='DateEventSchedule', + ), + ] diff --git a/src/api/models/EventScheduleModel.py b/src/api/models/EventScheduleModel.py index f4325eb..847c2d6 100644 --- a/src/api/models/EventScheduleModel.py +++ b/src/api/models/EventScheduleModel.py @@ -1,7 +1,9 @@ -from django.db import models -from django.core.validators import validate_comma_separated_integer_list +from enum import Enum + from api.models.EateryModel import EateryStore from api.models.MenuModel import MenuStore +from django.core.validators import validate_comma_separated_integer_list +from django.db import models class EventDescription(models.TextChoices): @@ -38,18 +40,15 @@ class Meta: unique_together = ("eatery", "event_description") -class DateEventSchedule(EventSchedule): - event_description = models.CharField( - choices=EventDescription.choices, max_length=10 - ) - menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - canonical_date = models.DateField() - start_timestamp = models.IntegerField() - end_timestamp = models.IntegerField() +class ExceptionType(Enum): + CLOSED = 0 + MODIFIED = 1 -class ClosedEventSchedule(EventSchedule): - event_description = models.CharField( - choices=EventDescription.choices, max_length=10 - ) - canonical_date = models.DateField() +class ScheduleException(models.Model): + id = models.IntegerField(primary_key=True) + parent = models.ForeignKey(RepeatingEventSchedule, on_delete=models.DO_NOTHING) + date = models.DateField() + exception_type = ExceptionType + start_time = models.DateField(blank=True, null=True) + end_time = models.DateField(blank=True, null=True) diff --git a/src/api/models/MenuModel.py b/src/api/models/MenuModel.py index af5fc87..2ddc00c 100644 --- a/src/api/models/MenuModel.py +++ b/src/api/models/MenuModel.py @@ -1,37 +1,42 @@ -from django.db import models from api.models.EateryModel import EateryStore +from django.db import models + class MenuStore(models.Model): id = models.IntegerField(primary_key=True) eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - name = models.CharField(max_length = 40) + name = models.CharField(max_length=40) class Meta: - unique_together = ('eatery', 'name') + unique_together = ("eatery", "name") + class CategoryStore(models.Model): id = models.IntegerField(primary_key=True) menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - category = models.CharField(max_length = 40) + category = models.CharField(max_length=40) class Meta: - unique_together = ('menu', 'category') + unique_together = ("menu", "category") + class ItemStore(models.Model): id = models.IntegerField(primary_key=True) eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) name = models.CharField(max_length=40) - description = models.CharField(max_length = 200, blank=True) - base_price = models.FloatField(null = True, blank=True) + description = models.CharField(max_length=200, blank=True) + base_price = models.FloatField(null=True, blank=True) + class SubItemStore(models.Model): id = models.IntegerField(primary_key=True) item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) - additional_price = models.FloatField(null = True, blank=True) - total_price = models.FloatField(null = True, blank=True) + additional_price = models.FloatField(null=True, blank=True) + total_price = models.FloatField(null=True, blank=True) name = models.CharField(max_length=40) item_subsection = models.CharField(max_length=40) + class CategoryItemAssociation(models.Model): id = models.IntegerField(primary_key=True) item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) diff --git a/src/api/models/__init__.py b/src/api/models/__init__.py index 7a9fc78..353e856 100644 --- a/src/api/models/__init__.py +++ b/src/api/models/__init__.py @@ -5,9 +5,8 @@ from .EateryModel import EateryStore from .EventScheduleModel import ( EventSchedule, - ClosedEventSchedule, - DateEventSchedule, RepeatingEventSchedule, + ScheduleException, ) from .MenuModel import ( MenuStore, diff --git a/src/api/serializers.py b/src/api/serializers.py index db9698e..92d4274 100644 --- a/src/api/serializers.py +++ b/src/api/serializers.py @@ -49,3 +49,8 @@ class RepeatingEventScheduleSerializer(serializers.ModelSerializer): class Meta: model = models.RepeatingEventSchedule fields = "__all__" + +class ScheduleExceptionSerializer(serializers.ModelSerializer): + class Meta: + model = models.ScheduleException + fields = "__all__" diff --git a/src/api/util/constants.py b/src/api/util/constants.py index a32ab7a..fa5cbe6 100644 --- a/src/api/util/constants.py +++ b/src/api/util/constants.py @@ -4,6 +4,7 @@ CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" + class SnapshotFileName(Enum): EATERY_STORE = "eatery_store.txt" ALERT_STORE = "alert_store.txt" @@ -17,6 +18,8 @@ class SnapshotFileName(Enum): DATE_EVENT_SCHEDULE = "date_event_schedule.txt" CLOSED_EVENT_SCHEDULE = "closed_event_schedule.txt" TRANSACTION_HISTORY = "transaction_history.txt" + SCHEDULE_EXCEPTION = "schedule_exception.txt" + def dining_id_to_internal_id(id: int): if id == 31: @@ -88,16 +91,19 @@ def dining_id_to_internal_id(id: int): else: return None + # Our transactions vendor def vendor_name_to_internal_id(vendor_eatery_name): - vendor_eatery_name = ''.join(c.lower() for c in vendor_eatery_name if c.isalpha()) + vendor_eatery_name = "".join(c.lower() for c in vendor_eatery_name if c.isalpha()) if vendor_eatery_name == "bearnecessities": return EateryID.BEAR_NECESSITIES elif vendor_eatery_name == "northstarmarketplace": return EateryID.NORTH_STAR_DINING elif vendor_eatery_name == "jansensmarket": return EateryID.JANSENS_MARKET - elif vendor_eatery_name == "stockinghallcafe" or vendor_eatery_name == "stockinghall": + elif ( + vendor_eatery_name == "stockinghallcafe" or vendor_eatery_name == "stockinghall" + ): return EateryID.DAIRY_BAR elif vendor_eatery_name == "marthas": return EateryID.MARTHAS_CAFE @@ -157,4 +163,4 @@ def vendor_name_to_internal_id(vendor_eatery_name): return EateryID.MACS_CAFE else: # TODO: Add a slack notif / flag that a wait time location was not recognized - return None \ No newline at end of file + return None From ada725525cb1b070d09fa4958cf909ff40205c39 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Wed, 13 Apr 2022 16:47:04 -0400 Subject: [PATCH 075/305] Added textchoices for exception_type and implemented DateSchedule --- src/api/datatype/Event.py | 59 ++++++++++++------- src/api/dfg/nodes/schedule/DateSchedule.py | 37 ++++++++++-- .../dfg/nodes/schedule/RepeatingSchedule.py | 1 + .../0009_scheduleexception_exception_type.py | 19 ++++++ src/api/models/EventScheduleModel.py | 8 +-- 5 files changed, 94 insertions(+), 30 deletions(-) create mode 100644 src/api/migrations/0009_scheduleexception_exception_type.py diff --git a/src/api/datatype/Event.py b/src/api/datatype/Event.py index d3a9175..419d073 100644 --- a/src/api/datatype/Event.py +++ b/src/api/datatype/Event.py @@ -1,39 +1,40 @@ -from typing import Optional from datetime import date, time -from api.datatype.Menu import Menu -from api.util.time import combined_timestamp +from typing import Optional import pytz +from api.datatype.Menu import Menu +from api.util.time import combined_timestamp class Event: - def __init__( - self, - description: str, - canonical_date: date, - start_timestamp: int, - end_timestamp: int, - menu: Menu + self, + description: str, + canonical_date: date, + start_timestamp: int, + end_timestamp: int, + menu: Menu, + generated_by: Optional[int] = None, ): self.description = description self.canonical_date = canonical_date self.start_timestamp = start_timestamp self.end_timestamp = end_timestamp self.menu = menu + self.generated_by = generated_by def to_json( self, tzinfo: Optional[pytz.timezone] = None, start: Optional[date] = None, - end: Optional[date] = None + end: Optional[date] = None, ): return { "description": self.description, "canonical_date": str(self.canonical_date), "start_timestamp": self.start_timestamp, "end_timestamp": self.end_timestamp, - "menu": self.menu.to_json() + "menu": self.menu.to_json(), } @staticmethod @@ -43,13 +44,19 @@ def from_json(event_json): canonical_date=date.fromisoformat(event_json["canonical_date"]), start_timestamp=event_json["start_timestamp"], end_timestamp=event_json["end_timestamp"], - menu=Menu.from_json(event_json["menu"]) + menu=Menu.from_json(event_json["menu"]), ) def __contains__(self, item: int): return self.start_timestamp <= item <= self.end_timestamp -def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Optional[date], end: Optional[date]): + +def filter_range( + events: list[Event], + tzinfo: Optional[pytz.timezone], + start: Optional[date], + end: Optional[date], +): if events is None: return [] @@ -58,16 +65,26 @@ def filter_range(events: list[Event], tzinfo: Optional[pytz.timezone], start: Op elif tzinfo is not None and start is not None and end is None: start_ts = combined_timestamp(start, time(), tzinfo) - return [event for event in events if ( - (start_ts in event) or start == event.canonical_date - )] + return [ + event + for event in events + if ((start_ts in event) or start == event.canonical_date) + ] elif tzinfo is not None and start is not None and end is not None: start_ts = combined_timestamp(start, time(), tzinfo) end_ts = combined_timestamp(end, time(), tzinfo) - return [event for event in events if ( - (start_ts in event) or (end_ts in event) or start <= event.canonical_date <= end - )] + return [ + event + for event in events + if ( + (start_ts in event) + or (end_ts in event) + or start <= event.canonical_date <= end + ) + ] else: - raise Exception(f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}") \ No newline at end of file + raise Exception( + f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}" + ) diff --git a/src/api/dfg/nodes/schedule/DateSchedule.py b/src/api/dfg/nodes/schedule/DateSchedule.py index 53d4476..756e9d6 100644 --- a/src/api/dfg/nodes/schedule/DateSchedule.py +++ b/src/api/dfg/nodes/schedule/DateSchedule.py @@ -1,16 +1,43 @@ -from api.dfg.nodes.DfgNode import DfgNode +from datetime import timedelta + from api.datatype.Eatery import Eatery, EateryID -# from eateries.models import DateEventSchedule +from api.datatype.Event import Event +from api.dfg.nodes.DfgNode import DfgNode +from api.models.EventScheduleModel import ExceptionType, ScheduleException -class DateSchedule(DfgNode): +class DateSchedule(DfgNode): def __init__(self, eatery_id: EateryID, cache): self.eatery_id = eatery_id self.cache = cache def __call__(self, *args, **kwargs) -> list[Eatery]: - # DateEventSchedule.objects.all() - return [] + if "date_exception" not in self.cache: + self.cache["date_exception"] = ScheduleException.objects.filter( + exception_type=ExceptionType.MODIFIED + ) + modified_schedules = [ + sched + for sched in self.cache["date_exception"] + if EateryID(sched["eatery_id"]) == self.eatery_id + ] + events = [] + date = kwargs.get("start") + while date <= kwargs.get("end"): + for sched in modified_schedules: + if sched["date"] == date: + events.append( + Event( + canonical_date=date, + generated_by=sched["parent"], + start_timestamp=sched["start_time"], + end_timestamp=sched["end_time"], + description=None, + menu=None, + ) + ) + date += timedelta(days=1) + return events def description(self): return "DateSchedule" diff --git a/src/api/dfg/nodes/schedule/RepeatingSchedule.py b/src/api/dfg/nodes/schedule/RepeatingSchedule.py index b60a618..6d8e3b7 100644 --- a/src/api/dfg/nodes/schedule/RepeatingSchedule.py +++ b/src/api/dfg/nodes/schedule/RepeatingSchedule.py @@ -47,6 +47,7 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: tzinfo=kwargs.get("tzinfo"), ), menu=self.cache["menus"][self.eatery_id][sched["menu_id"]], + generated_by=sched["id"], ) ) date += timedelta(days=1) diff --git a/src/api/migrations/0009_scheduleexception_exception_type.py b/src/api/migrations/0009_scheduleexception_exception_type.py new file mode 100644 index 0000000..29349f3 --- /dev/null +++ b/src/api/migrations/0009_scheduleexception_exception_type.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0 on 2022-04-13 20:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0008_scheduleexception_remove_dateeventschedule_eatery_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='scheduleexception', + name='exception_type', + field=models.CharField(choices=[('closed', 'Closed'), ('modified', 'Modified')], default='modified', max_length=10), + preserve_default=False, + ), + ] diff --git a/src/api/models/EventScheduleModel.py b/src/api/models/EventScheduleModel.py index 847c2d6..584a9ab 100644 --- a/src/api/models/EventScheduleModel.py +++ b/src/api/models/EventScheduleModel.py @@ -40,15 +40,15 @@ class Meta: unique_together = ("eatery", "event_description") -class ExceptionType(Enum): - CLOSED = 0 - MODIFIED = 1 +class ExceptionType(models.TextChoices): + CLOSED = "closed" + MODIFIED = "modified" class ScheduleException(models.Model): id = models.IntegerField(primary_key=True) parent = models.ForeignKey(RepeatingEventSchedule, on_delete=models.DO_NOTHING) date = models.DateField() - exception_type = ExceptionType + exception_type = models.CharField(max_length=10, choices=ExceptionType.choices) start_time = models.DateField(blank=True, null=True) end_time = models.DateField(blank=True, null=True) From e431b9f12a0f046f910b21d18eb198307ad2d7f3 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sat, 16 Apr 2022 23:56:00 -0400 Subject: [PATCH 076/305] Add finishing touches to modified exceptions --- src/api/datatype/Event.py | 6 +- src/api/dfg/nodes/macros/EateryEvents.py | 7 +- src/api/dfg/nodes/macros/LeftMergeEvents.py | 19 +++--- .../nodes/macros/LeftMergeRegularEvents.py | 11 ++++ .../nodes/macros/LeftMergeRepeatedEvents.py | 12 ++++ .../dfg/nodes/schedule/CornellDiningEvents.py | 64 ++++++++++--------- src/api/dfg/nodes/schedule/DateSchedule.py | 17 +++-- ...ter_scheduleexception_end_time_and_more.py | 23 +++++++ .../0011_scheduleexception_eatery.py | 20 ++++++ src/api/models/EventScheduleModel.py | 7 +- 10 files changed, 133 insertions(+), 53 deletions(-) create mode 100644 src/api/dfg/nodes/macros/LeftMergeRegularEvents.py create mode 100644 src/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py create mode 100644 src/api/migrations/0010_alter_scheduleexception_end_time_and_more.py create mode 100644 src/api/migrations/0011_scheduleexception_eatery.py diff --git a/src/api/datatype/Event.py b/src/api/datatype/Event.py index 419d073..bcb1869 100644 --- a/src/api/datatype/Event.py +++ b/src/api/datatype/Event.py @@ -34,7 +34,8 @@ def to_json( "canonical_date": str(self.canonical_date), "start_timestamp": self.start_timestamp, "end_timestamp": self.end_timestamp, - "menu": self.menu.to_json(), + "menu": self.menu.to_json() if self.menu else None, + "generated_by": self.generated_by, } @staticmethod @@ -45,6 +46,9 @@ def from_json(event_json): start_timestamp=event_json["start_timestamp"], end_timestamp=event_json["end_timestamp"], menu=Menu.from_json(event_json["menu"]), + generated_by=event_json["generated_by"] + if "generated_by" in event_json + else None, ) def __contains__(self, item: int): diff --git a/src/api/dfg/nodes/macros/EateryEvents.py b/src/api/dfg/nodes/macros/EateryEvents.py index ac44530..a9bf720 100644 --- a/src/api/dfg/nodes/macros/EateryEvents.py +++ b/src/api/dfg/nodes/macros/EateryEvents.py @@ -1,6 +1,7 @@ from api.datatype.Eatery import EateryID from api.dfg.nodes.DfgNode import DfgNode -from api.dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents +from api.dfg.nodes.macros.LeftMergeRegularEvents import LeftMergeRegularEvents +from api.dfg.nodes.macros.LeftMergeRepeatedEvents import LeftMergeRepeatedEvents from api.dfg.nodes.schedule.CacheMenuInjection import CacheMenuInjection from api.dfg.nodes.schedule.ClosedSchedule import ClosedSchedule from api.dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents @@ -15,9 +16,9 @@ def __init__(self, eatery_id: EateryID, cache): self.macro = CacheMenuInjection( ClosedSchedule( eatery_id, - LeftMergeEvents( + LeftMergeRepeatedEvents( DateSchedule(eatery_id, cache), - LeftMergeEvents( + LeftMergeRegularEvents( RepeatingSchedule(eatery_id, cache), CornellDiningEvents(eatery_id, cache), ), diff --git a/src/api/dfg/nodes/macros/LeftMergeEvents.py b/src/api/dfg/nodes/macros/LeftMergeEvents.py index db43fd3..fbcf348 100644 --- a/src/api/dfg/nodes/macros/LeftMergeEvents.py +++ b/src/api/dfg/nodes/macros/LeftMergeEvents.py @@ -1,28 +1,25 @@ from api.dfg.nodes.DfgNode import DfgNode - -from api.dfg.nodes.system.ConvertToJson import ConvertToJson from api.dfg.nodes.system.ConvertFromJson import EventFromJson +from api.dfg.nodes.system.ConvertToJson import ConvertToJson from api.dfg.nodes.system.LeftMerge import LeftMerge + # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if # conflict) class LeftMergeEvents(DfgNode): - def __init__(self, left: DfgNode, right: DfgNode): + def __init__(self, left: DfgNode, right: DfgNode, attr_lst: list[str]): def comparator(left, right): - left_val = (left["canonical_date"], left["description"]) - right_val = (right["canonical_date"], right["description"]) + left_val = [left[attr] for attr in attr_lst] + right_val = [right[attr] for attr in attr_lst] if left_val == right_val: return 0 elif left_val < right_val: return -1 else: return 1 + self.macro = EventFromJson( - LeftMerge( - ConvertToJson(left), - ConvertToJson(right), - comparator - ) + LeftMerge(ConvertToJson(left), ConvertToJson(right), comparator) ) def children(self): @@ -32,4 +29,4 @@ def __call__(self, *args, **kwargs): return self.macro(*args, **kwargs) def description(self): - return "LeftMergeEvents" \ No newline at end of file + return "LeftMergeEvents" diff --git a/src/api/dfg/nodes/macros/LeftMergeRegularEvents.py b/src/api/dfg/nodes/macros/LeftMergeRegularEvents.py new file mode 100644 index 0000000..712187c --- /dev/null +++ b/src/api/dfg/nodes/macros/LeftMergeRegularEvents.py @@ -0,0 +1,11 @@ +from api.dfg.nodes.DfgNode import DfgNode +from api.dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents + + +class LeftMergeRegularEvents(LeftMergeEvents): + """ + Merges two lists of Event objects, regardless of how they were generated + """ + + def __init__(self, left: DfgNode, right: DfgNode): + super().__init__(left, right, ["canonical_date", "description"]) diff --git a/src/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py b/src/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py new file mode 100644 index 0000000..014da8f --- /dev/null +++ b/src/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py @@ -0,0 +1,12 @@ +from api.dfg.nodes.DfgNode import DfgNode +from api.dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents + + +class LeftMergeRepeatedEvents(LeftMergeEvents): + """ + Merges two lists of Event objects, one of which consists of a list of events generated by a + repeated schedule and the other of which consists of a list of events that are exceptions + """ + + def __init__(self, left: DfgNode, right: DfgNode): + super().__init__(left, right, ["canonical_date", "generated_by"]) diff --git a/src/api/dfg/nodes/schedule/CornellDiningEvents.py b/src/api/dfg/nodes/schedule/CornellDiningEvents.py index f0b63a4..ef84211 100644 --- a/src/api/dfg/nodes/schedule/CornellDiningEvents.py +++ b/src/api/dfg/nodes/schedule/CornellDiningEvents.py @@ -1,16 +1,16 @@ -from api.dfg.nodes.DfgNode import DfgNode +from datetime import date + +import requests from api.datatype.Eatery import Eatery, EateryID from api.datatype.Event import Event from api.datatype.Menu import Menu from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem -from api.util.constants import dining_id_to_internal_id, CORNELL_DINING_URL +from api.dfg.nodes.DfgNode import DfgNode +from api.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id -from datetime import date -import requests class CornellDiningEvents(DfgNode): - def __init__(self, eatery_id: EateryID, cache): self.eatery_id = eatery_id self.cache = cache @@ -35,22 +35,23 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: @staticmethod def parse_eatery(json_eatery: dict) -> Eatery: is_cafe = "Cafe" in { - eatery_type["descr"] - for eatery_type in json_eatery["eateryTypes"] + eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] } return Eatery( id=dining_id_to_internal_id(json_eatery["id"]), events=CornellDiningEvents.eatery_events_from_json( json_operating_hours=json_eatery["operatingHours"], json_dining_items=json_eatery["diningItems"], - is_cafe=is_cafe - ) + is_cafe=is_cafe, + ), ) + @staticmethod - def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: + def eatery_events_from_json( + json_operating_hours: list, json_dining_items: list, is_cafe: bool + ) -> list[Event]: json_operating_hours = sorted( - json_operating_hours, - key=lambda json_date_events: json_date_events["date"] + json_operating_hours, key=lambda json_date_events: json_date_events["date"] ) events = [] @@ -58,13 +59,17 @@ def eatery_events_from_json(json_operating_hours: list, json_dining_items: list, canonical_date = date.fromisoformat(json_date_events["date"]) for json_event in json_date_events["events"]: - events.append(Event( - canonical_date=canonical_date, - description=json_event["descr"], - start_timestamp=json_event["startTimestamp"], - end_timestamp=json_event["endTimestamp"], - menu=CornellDiningEvents.eatery_menu_from_json(json_event["menu"], json_dining_items, is_cafe) - )) + events.append( + Event( + canonical_date=canonical_date, + description=json_event["descr"], + start_timestamp=json_event["startTimestamp"], + end_timestamp=json_event["endTimestamp"], + menu=CornellDiningEvents.eatery_menu_from_json( + json_event["menu"], json_dining_items, is_cafe + ), + ) + ) return events @@ -79,9 +84,11 @@ def eatery_menu_from_json(json_menu: list, json_dining_items: list, is_cafe: boo def cafe_menu_from_json(json_dining_items: list) -> Menu: category_map = {} for item in json_dining_items: - if item['category'] not in category_map: - category_map[item['category']] = [] - category_map[item['category']].append(MenuItem(healthy=item['healthy'], name=item['item'])) + if item["category"] not in category_map: + category_map[item["category"]] = [] + category_map[item["category"]].append( + MenuItem(healthy=item["healthy"], name=item["item"]) + ) categories = [] for category_name in category_map: categories.append(MenuCategory(category_name, category_map[category_name])) @@ -90,8 +97,7 @@ def cafe_menu_from_json(json_dining_items: list) -> Menu: @staticmethod def dining_hall_menu_from_json(json_menu: list) -> Menu: json_menu = sorted( - json_menu, - key=lambda json_menu_category: json_menu_category["sortIdx"] + json_menu, key=lambda json_menu_category: json_menu_category["sortIdx"] ) menu_categories = [] @@ -101,13 +107,11 @@ def dining_hall_menu_from_json(json_menu: list) -> Menu: for json_item in json_menu_category["items"] ] - menu_categories.append(MenuCategory( - category=json_menu_category["category"], - items=items - )) + menu_categories.append( + MenuCategory(category=json_menu_category["category"], items=items) + ) return Menu(categories=menu_categories) - def description(self): - return "CornellDiningEvents" \ No newline at end of file + return "CornellDiningEvents" diff --git a/src/api/dfg/nodes/schedule/DateSchedule.py b/src/api/dfg/nodes/schedule/DateSchedule.py index 756e9d6..28a9af4 100644 --- a/src/api/dfg/nodes/schedule/DateSchedule.py +++ b/src/api/dfg/nodes/schedule/DateSchedule.py @@ -4,6 +4,7 @@ from api.datatype.Event import Event from api.dfg.nodes.DfgNode import DfgNode from api.models.EventScheduleModel import ExceptionType, ScheduleException +from api.util.time import combined_timestamp class DateSchedule(DfgNode): @@ -15,7 +16,7 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: if "date_exception" not in self.cache: self.cache["date_exception"] = ScheduleException.objects.filter( exception_type=ExceptionType.MODIFIED - ) + ).values() modified_schedules = [ sched for sched in self.cache["date_exception"] @@ -29,9 +30,17 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: events.append( Event( canonical_date=date, - generated_by=sched["parent"], - start_timestamp=sched["start_time"], - end_timestamp=sched["end_time"], + generated_by=sched["parent_id"], + start_timestamp=combined_timestamp( + date=date, + time=sched["start_time"], + tzinfo=kwargs.get("tzinfo"), + ), + end_timestamp=combined_timestamp( + date=date, + time=sched["end_time"], + tzinfo=kwargs.get("tzinfo"), + ), description=None, menu=None, ) diff --git a/src/api/migrations/0010_alter_scheduleexception_end_time_and_more.py b/src/api/migrations/0010_alter_scheduleexception_end_time_and_more.py new file mode 100644 index 0000000..9bf7634 --- /dev/null +++ b/src/api/migrations/0010_alter_scheduleexception_end_time_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0 on 2022-04-17 03:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0009_scheduleexception_exception_type'), + ] + + operations = [ + migrations.AlterField( + model_name='scheduleexception', + name='end_time', + field=models.TimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='scheduleexception', + name='start_time', + field=models.TimeField(blank=True, null=True), + ), + ] diff --git a/src/api/migrations/0011_scheduleexception_eatery.py b/src/api/migrations/0011_scheduleexception_eatery.py new file mode 100644 index 0000000..d9dd68b --- /dev/null +++ b/src/api/migrations/0011_scheduleexception_eatery.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0 on 2022-04-17 03:49 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0010_alter_scheduleexception_end_time_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='scheduleexception', + name='eatery', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore'), + preserve_default=False, + ), + ] diff --git a/src/api/models/EventScheduleModel.py b/src/api/models/EventScheduleModel.py index 584a9ab..25aed85 100644 --- a/src/api/models/EventScheduleModel.py +++ b/src/api/models/EventScheduleModel.py @@ -45,10 +45,9 @@ class ExceptionType(models.TextChoices): MODIFIED = "modified" -class ScheduleException(models.Model): - id = models.IntegerField(primary_key=True) +class ScheduleException(EventSchedule): parent = models.ForeignKey(RepeatingEventSchedule, on_delete=models.DO_NOTHING) date = models.DateField() exception_type = models.CharField(max_length=10, choices=ExceptionType.choices) - start_time = models.DateField(blank=True, null=True) - end_time = models.DateField(blank=True, null=True) + start_time = models.TimeField(blank=True, null=True) + end_time = models.TimeField(blank=True, null=True) From 8f4a7affb947282c2c41c5d6669463afdd037a52 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 17 Apr 2022 17:46:39 -0400 Subject: [PATCH 077/305] Implement closed schedules --- .github/CODEOWNERS | 2 +- src/api/dfg/nodes/macros/EateryEvents.py | 4 +-- src/api/dfg/nodes/schedule/ClosedSchedule.py | 36 +++++++++++++++++-- .../{DateSchedule.py => ModifiedSchedules.py} | 4 +-- 4 files changed, 39 insertions(+), 7 deletions(-) rename src/api/dfg/nodes/schedule/{DateSchedule.py => ModifiedSchedules.py} (96%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5f3eb94..947ec15 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @Archit404Error @connorreinhold @chalo2000 @markim21 \ No newline at end of file +* @Archit404Error @connorreinhold @chalo2000 @markim21 @alanna-zhou \ No newline at end of file diff --git a/src/api/dfg/nodes/macros/EateryEvents.py b/src/api/dfg/nodes/macros/EateryEvents.py index a9bf720..fdd5165 100644 --- a/src/api/dfg/nodes/macros/EateryEvents.py +++ b/src/api/dfg/nodes/macros/EateryEvents.py @@ -5,7 +5,7 @@ from api.dfg.nodes.schedule.CacheMenuInjection import CacheMenuInjection from api.dfg.nodes.schedule.ClosedSchedule import ClosedSchedule from api.dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents -from api.dfg.nodes.schedule.DateSchedule import DateSchedule +from api.dfg.nodes.schedule.ModifiedSchedules import ModifiedSchedules from api.dfg.nodes.schedule.RepeatingSchedule import RepeatingSchedule @@ -17,7 +17,7 @@ def __init__(self, eatery_id: EateryID, cache): ClosedSchedule( eatery_id, LeftMergeRepeatedEvents( - DateSchedule(eatery_id, cache), + ModifiedSchedules(eatery_id, cache), LeftMergeRegularEvents( RepeatingSchedule(eatery_id, cache), CornellDiningEvents(eatery_id, cache), diff --git a/src/api/dfg/nodes/schedule/ClosedSchedule.py b/src/api/dfg/nodes/schedule/ClosedSchedule.py index 5536a3a..963553b 100644 --- a/src/api/dfg/nodes/schedule/ClosedSchedule.py +++ b/src/api/dfg/nodes/schedule/ClosedSchedule.py @@ -1,5 +1,6 @@ from api.datatype.Eatery import Eatery, EateryID from api.dfg.nodes.DfgNode import DfgNode +from api.models.EventScheduleModel import ExceptionType, ScheduleException class ClosedSchedule(DfgNode): @@ -14,8 +15,39 @@ def __init__(self, eatery_id: EateryID, child: DfgNode, cache): self.cache = cache def __call__(self, *args, **kwargs) -> list[Eatery]: - # ClosedEventSchedule.objects.all() - return self.child(*args, **kwargs) + if "closed_exception" not in self.cache: + self.cache["closed_exception"] = ScheduleException.objects.filter( + exception_type=ExceptionType.CLOSED + ).values() + + closed_scheds = [ + sched + for sched in self.cache["closed_exception"] + if EateryID(sched["eatery_id"]) == self.eatery_id + ] + + unfiltered_events = self.child(*args, **kwargs) + filtered = [] + + for event in unfiltered_events: + # append directly if not a repeating schedule + if not event.generated_by: + filtered.append(event) + continue + + # check to see if repeating schedule needs to be thrown out + found = False + for sched in closed_scheds: + if ( + event.generated_by == sched["parent_id"] + and event.canonical_date == sched["date"] + ): + found = True + break + if not found: + filtered.append(event) + + return filtered def children(self): return [self.child] diff --git a/src/api/dfg/nodes/schedule/DateSchedule.py b/src/api/dfg/nodes/schedule/ModifiedSchedules.py similarity index 96% rename from src/api/dfg/nodes/schedule/DateSchedule.py rename to src/api/dfg/nodes/schedule/ModifiedSchedules.py index 28a9af4..1369ca8 100644 --- a/src/api/dfg/nodes/schedule/DateSchedule.py +++ b/src/api/dfg/nodes/schedule/ModifiedSchedules.py @@ -7,7 +7,7 @@ from api.util.time import combined_timestamp -class DateSchedule(DfgNode): +class ModifiedSchedules(DfgNode): def __init__(self, eatery_id: EateryID, cache): self.eatery_id = eatery_id self.cache = cache @@ -49,4 +49,4 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: return events def description(self): - return "DateSchedule" + return "ModifiedSchedules" From 1e469cdb5c79efe8491cf303bfab12698fe6aa7a Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 17 Apr 2022 20:47:20 -0400 Subject: [PATCH 078/305] Added necessary fixes --- src/api/datatype/Event.py | 30 ++++++++++++++++++--- src/api/dfg/nodes/macros/LeftMergeEvents.py | 4 --- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/api/datatype/Event.py b/src/api/datatype/Event.py index bcb1869..e0d1b7b 100644 --- a/src/api/datatype/Event.py +++ b/src/api/datatype/Event.py @@ -7,6 +7,25 @@ class Event: + """ + Represents an event on a particular date + + Attributes + ---------- + description + the event description + canonical date + the date on which the event started + start_timestamp + timestamp of the exact time the event started + end_timestamp + timestamp of the exact time the event ended + menu + the menu associated with the event + generated_by + id of repeating schedule that generated this event (if exists) + """ + def __init__( self, description: str, @@ -46,9 +65,7 @@ def from_json(event_json): start_timestamp=event_json["start_timestamp"], end_timestamp=event_json["end_timestamp"], menu=Menu.from_json(event_json["menu"]), - generated_by=event_json["generated_by"] - if "generated_by" in event_json - else None, + generated_by=event_json.get("generated_by"), ) def __contains__(self, item: int): @@ -61,6 +78,13 @@ def filter_range( start: Optional[date], end: Optional[date], ): + """ + Filters a list of events based on the parameters provided. + If neither start nor end is provided no filtering occurs. + If tzinfo and start are provided, returns a new list containing all events that start on start + If all 3 optional parameters are provided, returns a new list containing all events + between start and end + """ if events is None: return [] diff --git a/src/api/dfg/nodes/macros/LeftMergeEvents.py b/src/api/dfg/nodes/macros/LeftMergeEvents.py index fbcf348..6fb26f2 100644 --- a/src/api/dfg/nodes/macros/LeftMergeEvents.py +++ b/src/api/dfg/nodes/macros/LeftMergeEvents.py @@ -4,8 +4,6 @@ from api.dfg.nodes.system.LeftMerge import LeftMerge -# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if -# conflict) class LeftMergeEvents(DfgNode): def __init__(self, left: DfgNode, right: DfgNode, attr_lst: list[str]): def comparator(left, right): @@ -13,8 +11,6 @@ def comparator(left, right): right_val = [right[attr] for attr in attr_lst] if left_val == right_val: return 0 - elif left_val < right_val: - return -1 else: return 1 From d1ee7ddb6bb36931fc8d1fadec9d2201d94fceac Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 17 Apr 2022 20:50:25 -0400 Subject: [PATCH 079/305] Move to .get syntax --- src/api/dfg/nodes/macros/LeftMergeEvents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/dfg/nodes/macros/LeftMergeEvents.py b/src/api/dfg/nodes/macros/LeftMergeEvents.py index 6fb26f2..2188d4b 100644 --- a/src/api/dfg/nodes/macros/LeftMergeEvents.py +++ b/src/api/dfg/nodes/macros/LeftMergeEvents.py @@ -7,8 +7,8 @@ class LeftMergeEvents(DfgNode): def __init__(self, left: DfgNode, right: DfgNode, attr_lst: list[str]): def comparator(left, right): - left_val = [left[attr] for attr in attr_lst] - right_val = [right[attr] for attr in attr_lst] + left_val = [left.get(attr) for attr in attr_lst] + right_val = [right.get(attr) for attr in attr_lst] if left_val == right_val: return 0 else: From a5c906a69694e5703ac6f8a9823f531dd295e93f Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Mon, 18 Apr 2022 20:49:00 -0400 Subject: [PATCH 080/305] Make eatery id optional in reports --- src/api/controllers/create_report.py | 15 ++++++++------- .../0012_alter_reportstore_eatery.py | 19 +++++++++++++++++++ src/api/models/ReportModel.py | 7 ++++--- src/api/views.py | 18 ++++++++++-------- 4 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 src/api/migrations/0012_alter_reportstore_eatery.py diff --git a/src/api/controllers/create_report.py b/src/api/controllers/create_report.py index b0e026f..16addf5 100644 --- a/src/api/controllers/create_report.py +++ b/src/api/controllers/create_report.py @@ -1,10 +1,12 @@ from datetime import datetime +from typing import Optional + from api.datatype.Eatery import EateryID from api.models import ReportStore -class CreateReportController: - def __init__(self, eatery_id: EateryID, type: str, content: str): +class CreateReportController: + def __init__(self, type: str, content: str, eatery_id: Optional[EateryID] = None): self.eatery_id = eatery_id self.type = type self.content = content @@ -12,9 +14,8 @@ def __init__(self, eatery_id: EateryID, type: str, content: str): def process(self): current_timestamp = datetime.now().timestamp() ReportStore.objects.create( - eatery_id = self.eatery_id.value, - type = self.type, - content = self.content, - created_timestamp = current_timestamp + eatery_id=self.eatery_id.value if self.eatery_id else None, + type=self.type, + content=self.content, + created_timestamp=current_timestamp, ) - \ No newline at end of file diff --git a/src/api/migrations/0012_alter_reportstore_eatery.py b/src/api/migrations/0012_alter_reportstore_eatery.py new file mode 100644 index 0000000..f6a6ccf --- /dev/null +++ b/src/api/migrations/0012_alter_reportstore_eatery.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0 on 2022-04-19 00:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0011_scheduleexception_eatery'), + ] + + operations = [ + migrations.AlterField( + model_name='reportstore', + name='eatery', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore'), + ), + ] diff --git a/src/api/models/ReportModel.py b/src/api/models/ReportModel.py index eea6e24..f28aaeb 100644 --- a/src/api/models/ReportModel.py +++ b/src/api/models/ReportModel.py @@ -1,8 +1,9 @@ -from django.db import models from api.models.EateryModel import EateryStore +from django.db import models + class ReportStore(models.Model): - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING, null=True) type = models.CharField(max_length=200) content = models.TextField() - created_timestamp = models.IntegerField() \ No newline at end of file + created_timestamp = models.IntegerField() diff --git a/src/api/views.py b/src/api/views.py index 48b3f9d..95e23bb 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -1,14 +1,15 @@ -from django.http import JsonResponse -from rest_framework.views import APIView +import json from datetime import date, timedelta + import pytz -import json +from django.http import JsonResponse +from rest_framework.views import APIView -from api.datatype.Eatery import EateryID -from api.dfg.main import main_dfg from api.controllers.create_report import CreateReportController from api.controllers.update_eatery import UpdateEateryController -from api.util.json import verify_json_fields, success_json, error_json, FieldType +from api.datatype.Eatery import EateryID +from api.dfg.main import main_dfg +from api.util.json import FieldType, error_json, success_json, verify_json_fields # Create your views here. @@ -34,16 +35,17 @@ def post(self, request): if not verify_json_fields( json_body, { - "eatery_id": FieldType.INT, "type": FieldType.STRING, "content": FieldType.STRING, }, ): return JsonResponse(error_json("Malformed Request")) + + id_provided = json_body.get("eatery_id") CreateReportController( - eatery_id=EateryID(json_body["eatery_id"]), type=json_body["type"], content=json_body["content"], + eatery_id=EateryID(id_provided) if id_provided else None, ).process() return JsonResponse(success_json("Reported")) From 697f9e90ab3f4181b30322bcab3efc7dcf08f0b0 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Mon, 18 Apr 2022 21:02:30 -0400 Subject: [PATCH 081/305] Edit defensive checks --- src/api/util/json.py | 31 ++++++++++++++----------------- src/api/views.py | 2 ++ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/api/util/json.py b/src/api/util/json.py index aec6d35..51f01a0 100644 --- a/src/api/util/json.py +++ b/src/api/util/json.py @@ -1,18 +1,21 @@ -from typing import Mapping from enum import Enum +from typing import List, Mapping, Optional from api.datatype.Eatery import EateryID + class FieldType(Enum): - INT = 'int' - STRING = 'str' + INT = "int" + STRING = "str" EATERYID = "eateryid" - -def verify_json_fields(json, field_type_map: Mapping[str, FieldType]): + +def verify_json_fields( + json, field_type_map: Mapping[str, FieldType], nullable: Optional[List] = [] +): for field in field_type_map: if field not in json: - return False + return field in nullable if field_type_map[field] is FieldType.INT: if not isinstance(json[field], int): return False @@ -22,19 +25,13 @@ def verify_json_fields(json, field_type_map: Mapping[str, FieldType]): elif field_type_map[field] is FieldType.EATERYID: if not isinstance(json[field], int) or EateryID(json[field]) == None: return False - + return True + def success_json(data): - return { - "success": True, - "data": data, - "error": None - } + return {"success": True, "data": data, "error": None} + def error_json(error: str): - return { - "success": False, - "data": None, - "error": error - } + return {"success": False, "data": None, "error": error} diff --git a/src/api/views.py b/src/api/views.py index 95e23bb..ccb4dc2 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -35,9 +35,11 @@ def post(self, request): if not verify_json_fields( json_body, { + "eatery_id": FieldType.INT or None, "type": FieldType.STRING, "content": FieldType.STRING, }, + ["eatery_id"], ): return JsonResponse(error_json("Malformed Request")) From bd7d6d59fd3e9c307031c9130527ab71664819e7 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Fri, 22 Apr 2022 19:41:34 -0400 Subject: [PATCH 082/305] Add deploy-dev.yml --- .github/workflows/deploy-dev.yml | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/deploy-dev.yml diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..b401b7a --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,44 @@ +name: Docker Build & Push and Deploy to eatery-blue-backend + +on: + push: + branches: [main] + +jobs: + path-context: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Get SHA + id: vars + run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + - name: Docker Build & Push + uses: docker/build-push-action@v2 + with: + context: ./ + file: ./Dockerfile + push: true + tags: cornellappdev/eatery-blue-dev:${{ steps.vars.outputs.sha_short }} + - name: Remote SSH and Deploy + uses: appleboy/ssh-action@master + env: + IMAGE_TAG: ${{ steps.vars.outputs.sha_short }} + with: + host: ${{ secrets.DEV_SERVER_HOST }} + username: ${{ secrets.SERVER_USERNAME }} + key: ${{ secrets.DEV_SERVER_KEY }} + script: | + export IMAGE_TAG=${{ steps.vars.outputs.sha_short }} + cd docker-compose + docker stack rm the-stack + sleep 20s + docker stack deploy -c docker-compose.yml the-stack + docker system prune -a From 0b93de95eb88a1489d687e1be88923a70bd993d4 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Fri, 22 Apr 2022 19:44:07 -0400 Subject: [PATCH 083/305] Create README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbf54d2 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# eatery-blue-backend + +This is the backend for eatery-blue-backend. From 1eaacf18c1d65b55537eb43b5e74849d927f1b72 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Fri, 22 Apr 2022 19:53:39 -0400 Subject: [PATCH 084/305] Fix deploy-dev.yml --- .github/workflows/deploy-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index b401b7a..67c5193 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -2,7 +2,7 @@ name: Docker Build & Push and Deploy to eatery-blue-backend on: push: - branches: [main] + branches: [master] jobs: path-context: From 4233c53b0ef5fba4c8b667528ba55f92ae15cc99 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Fri, 22 Apr 2022 20:25:23 -0400 Subject: [PATCH 085/305] Add docker-compose from server --- docker-compose.server.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docker-compose.server.yml diff --git a/docker-compose.server.yml b/docker-compose.server.yml new file mode 100644 index 0000000..f7aa260 --- /dev/null +++ b/docker-compose.server.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + app: + image: cornellappdev/eatery-blue-backend:${IMAGE_TAG} + env_file: .env + command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000" + ports: + - "8000:8000" + restart: always \ No newline at end of file From 41b145345f9ebd0976d40b76045f269ddc016710 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sat, 23 Apr 2022 18:38:24 -0400 Subject: [PATCH 086/305] Pulled out eatery MVC --- src/api/admin.py | 3 +- src/api/dfg/nodes/EateriesFromDB.py | 48 +++++++++++------ .../management/commands/export_db_snapshot.py | 3 +- .../management/commands/ingest_db_snapshot.py | 10 ++-- ..._eatery_alter_itemstore_eatery_and_more.py | 53 +++++++++++++++++++ src/api/models/AlertModel.py | 3 +- src/api/models/EateryModel.py | 22 -------- src/api/models/EventScheduleModel.py | 2 +- src/api/models/MenuModel.py | 2 +- src/api/models/ReportModel.py | 2 +- src/api/models/TransactionModel.py | 7 +-- src/api/models/__init__.py | 13 ++--- src/api/serializers.py | 7 +-- src/api/urls.py | 5 +- src/api/util/json.py | 4 ++ src/api/views.py | 21 -------- src/eatery/__init__.py | 0 src/eatery/admin.py | 3 ++ src/eatery/apps.py | 6 +++ .../controllers/update_eatery.py | 33 ++++-------- src/eatery/migrations/0001_initial.py | 31 +++++++++++ src/eatery/migrations/__init__.py | 0 src/eatery/models.py | 25 +++++++++ src/eatery/serializers.py | 9 ++++ src/eatery/tests.py | 3 ++ src/eatery/views.py | 45 ++++++++++++++++ src/eatery_blue_backend/settings.py | 3 +- 27 files changed, 251 insertions(+), 112 deletions(-) create mode 100644 src/api/migrations/0013_alter_alertstore_eatery_alter_itemstore_eatery_and_more.py delete mode 100644 src/api/models/EateryModel.py create mode 100644 src/eatery/__init__.py create mode 100644 src/eatery/admin.py create mode 100644 src/eatery/apps.py rename src/{api => eatery}/controllers/update_eatery.py (79%) create mode 100644 src/eatery/migrations/0001_initial.py create mode 100644 src/eatery/migrations/__init__.py create mode 100644 src/eatery/models.py create mode 100644 src/eatery/serializers.py create mode 100644 src/eatery/tests.py create mode 100644 src/eatery/views.py diff --git a/src/api/admin.py b/src/api/admin.py index 499455c..e519476 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -1,10 +1,11 @@ +import eatery.models as eatery_models from django.contrib import admin import api.models as models # Register your models here. -admin.site.register(models.EateryStore) +admin.site.register(eatery_models.EateryStore) admin.site.register(models.MenuStore) admin.site.register(models.ItemStore) admin.site.register(models.SubItemStore) diff --git a/src/api/dfg/nodes/EateriesFromDB.py b/src/api/dfg/nodes/EateriesFromDB.py index c333289..51a7633 100644 --- a/src/api/dfg/nodes/EateriesFromDB.py +++ b/src/api/dfg/nodes/EateriesFromDB.py @@ -3,29 +3,42 @@ from api.datatype.Eatery import Eatery, EateryID from api.datatype.EateryAlert import EateryAlert from api.dfg.nodes.DfgNode import DfgNode -from api.models import EateryStore, AlertStore -from api.serializers import EateryStoreSerializer, AlertStoreSerializer - +from api.models import AlertStore +from api.serializers import AlertStoreSerializer +from eatery.models import EateryStore +from eatery.serializers import EateryStoreSerializer # eventually need to deprecate this for a custom DB backend storing all of the overrides -class EateriesFromDB(DfgNode): +class EateriesFromDB(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: eateries = EateryStore.objects.all() serialized_eateries = EateryStoreSerializer(data=eateries, many=True) serialized_eateries.is_valid() - alerts = AlertStore.objects.filter(end_timestamp__gte=datetime.now().timestamp(), start_timestamp__lte=datetime.now().timestamp()) + alerts = AlertStore.objects.filter( + end_timestamp__gte=datetime.now().timestamp(), + start_timestamp__lte=datetime.now().timestamp(), + ) serialized_alerts = AlertStoreSerializer(data=alerts, many=True) serialized_alerts.is_valid() - return list(map(lambda x: EateriesFromDB.eatery_from_serialized(x, serialized_alerts.data), serialized_eateries.data)) + return list( + map( + lambda x: EateriesFromDB.eatery_from_serialized( + x, serialized_alerts.data + ), + serialized_eateries.data, + ) + ) @staticmethod def none_repr(str): return None if str == None or len(str) == 0 else str @staticmethod - def eatery_from_serialized(serialized_eatery: dict, serialized_alerts: list[dict]) -> Eatery: + def eatery_from_serialized( + serialized_eatery: dict, serialized_alerts: list[dict] + ) -> Eatery: return Eatery( id=EateryID(serialized_eatery["id"]), name=EateriesFromDB.none_repr(serialized_eatery["name"]), @@ -37,15 +50,23 @@ def eatery_from_serialized(serialized_eatery: dict, serialized_alerts: list[dict longitude=serialized_eatery["longitude"], payment_accepts_cash=serialized_eatery["payment_accepts_cash"], payment_accepts_brbs=serialized_eatery["payment_accepts_brbs"], - payment_accepts_meal_swipes=serialized_eatery["payment_accepts_meal_swipes"], + payment_accepts_meal_swipes=serialized_eatery[ + "payment_accepts_meal_swipes" + ], location=EateriesFromDB.none_repr(serialized_eatery["location"]), - online_order_url=EateriesFromDB.none_repr(serialized_eatery["online_order_url"]), - alerts = EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts) + online_order_url=EateriesFromDB.none_repr( + serialized_eatery["online_order_url"] + ), + alerts=EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts), ) @staticmethod def alerts(eatery_id: int, serialized_alerts: list[dict]): - return [EateriesFromDB.alert_from_serialized(alert) for alert in serialized_alerts if alert["eatery"] == eatery_id] + return [ + EateriesFromDB.alert_from_serialized(alert) + for alert in serialized_alerts + if alert["eatery"] == eatery_id + ] @staticmethod def alert_from_serialized(serialized_alert: dict): @@ -53,11 +74,8 @@ def alert_from_serialized(serialized_alert: dict): id=serialized_alert["id"], description=serialized_alert["description"], start_timestamp=serialized_alert["start_timestamp"], - end_timestamp=serialized_alert["end_timestamp"] + end_timestamp=serialized_alert["end_timestamp"], ) def description(self): return "EateriesFromDB" - - - diff --git a/src/api/management/commands/export_db_snapshot.py b/src/api/management/commands/export_db_snapshot.py index beb5ca2..8918a36 100644 --- a/src/api/management/commands/export_db_snapshot.py +++ b/src/api/management/commands/export_db_snapshot.py @@ -4,6 +4,7 @@ import api.models as models import api.serializers as serializers +import eatery.serializers as eatery_serializers import pytz from api.util.constants import SnapshotFileName from django.core.management.base import BaseCommand @@ -29,7 +30,7 @@ def handle(self, *args, **options): eateries = models.EateryStore.objects.all() self.write_to_file( - serializers.EateryStoreSerializer, + eatery_serializers.EateryStoreSerializer, eateries, folder_path, SnapshotFileName.EATERY_STORE, diff --git a/src/api/management/commands/ingest_db_snapshot.py b/src/api/management/commands/ingest_db_snapshot.py index adfe06c..e978dc0 100644 --- a/src/api/management/commands/ingest_db_snapshot.py +++ b/src/api/management/commands/ingest_db_snapshot.py @@ -1,8 +1,10 @@ -from django.core.management.base import BaseCommand -from api.util.constants import SnapshotFileName -import api.serializers as serializers import json +import api.serializers as serializers +import eatery.serializers as eatery_serializers +from api.util.constants import SnapshotFileName +from django.core.management.base import BaseCommand + class Command(BaseCommand): help = ( @@ -26,7 +28,7 @@ def ingest_data(self, serializer, folder_path: str, file_name: SnapshotFileName) def handle(self, *args, **options): folder_path = options["input"] self.ingest_data( - serializers.EateryStoreSerializer, + eatery_serializers.EateryStoreSerializer, folder_path, SnapshotFileName.EATERY_STORE, ) diff --git a/src/api/migrations/0013_alter_alertstore_eatery_alter_itemstore_eatery_and_more.py b/src/api/migrations/0013_alter_alertstore_eatery_alter_itemstore_eatery_and_more.py new file mode 100644 index 0000000..f65d906 --- /dev/null +++ b/src/api/migrations/0013_alter_alertstore_eatery_alter_itemstore_eatery_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 4.0 on 2022-04-23 22:19 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0001_initial'), + ('api', '0012_alter_reportstore_eatery'), + ] + + operations = [ + migrations.AlterField( + model_name='alertstore', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='itemstore', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='menustore', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='repeatingeventschedule', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='reportstore', + name='eatery', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='scheduleexception', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='transactionhistorystore', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.DeleteModel( + name='EateryStore', + ), + ] diff --git a/src/api/models/AlertModel.py b/src/api/models/AlertModel.py index 4908d57..d8cc5de 100644 --- a/src/api/models/AlertModel.py +++ b/src/api/models/AlertModel.py @@ -1,5 +1,6 @@ from django.db import models -from api.models.EateryModel import EateryStore +from eatery.models import EateryStore + class AlertStore(models.Model): id = models.IntegerField(primary_key=True) diff --git a/src/api/models/EateryModel.py b/src/api/models/EateryModel.py deleted file mode 100644 index 6598787..0000000 --- a/src/api/models/EateryModel.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.db import models - -class EateryStore(models.Model): - class CampusArea(models.TextChoices): - WEST = 'West' - NORTH = 'North' - CENTRAL = 'Central' - COLLEGETOWN = 'Collegetown' - NONE = '' - - id = models.IntegerField(primary_key=True) - name = models.CharField(max_length=40, blank=True) - menu_summary = models.CharField(max_length = 60, blank=True) - image_url = models.URLField(blank=True) - location = models.CharField(max_length=30, blank=True) - campus_area = models.CharField(max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True) - online_order_url = models.URLField(blank=True) - latitude = models.FloatField(null = True, blank=True) - longitude = models.FloatField(null = True, blank=True) - payment_accepts_meal_swipes = models.BooleanField(null = True, blank=True) - payment_accepts_brbs = models.BooleanField(null = True, blank=True) - payment_accepts_cash = models.BooleanField(null = True, blank=True) diff --git a/src/api/models/EventScheduleModel.py b/src/api/models/EventScheduleModel.py index 25aed85..1280fed 100644 --- a/src/api/models/EventScheduleModel.py +++ b/src/api/models/EventScheduleModel.py @@ -1,9 +1,9 @@ from enum import Enum -from api.models.EateryModel import EateryStore from api.models.MenuModel import MenuStore from django.core.validators import validate_comma_separated_integer_list from django.db import models +from eatery.models import EateryStore class EventDescription(models.TextChoices): diff --git a/src/api/models/MenuModel.py b/src/api/models/MenuModel.py index 2ddc00c..6d21c3c 100644 --- a/src/api/models/MenuModel.py +++ b/src/api/models/MenuModel.py @@ -1,5 +1,5 @@ -from api.models.EateryModel import EateryStore from django.db import models +from eatery.models import EateryStore class MenuStore(models.Model): diff --git a/src/api/models/ReportModel.py b/src/api/models/ReportModel.py index f28aaeb..86ab329 100644 --- a/src/api/models/ReportModel.py +++ b/src/api/models/ReportModel.py @@ -1,5 +1,5 @@ -from api.models.EateryModel import EateryStore from django.db import models +from eatery.models import EateryStore class ReportStore(models.Model): diff --git a/src/api/models/TransactionModel.py b/src/api/models/TransactionModel.py index a5a537f..4ce0976 100644 --- a/src/api/models/TransactionModel.py +++ b/src/api/models/TransactionModel.py @@ -1,13 +1,14 @@ from django.db import models -from api.models.EateryModel import EateryStore +from eatery.models import EateryStore # [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] class TransactionHistoryStore(models.Model): class Meta: - unique_together = ('eatery_id', 'block_end_time', 'canonical_date') - indexes = [models.Index(fields=['canonical_date'])] + unique_together = ("eatery_id", "block_end_time", "canonical_date") + indexes = [models.Index(fields=["canonical_date"])] + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) canonical_date = models.DateField() block_end_time = models.TimeField() diff --git a/src/api/models/__init__.py b/src/api/models/__init__.py index 353e856..c03ca98 100644 --- a/src/api/models/__init__.py +++ b/src/api/models/__init__.py @@ -1,19 +1,16 @@ # Need to expose models to django. # In this app, models should be imported directly from api.models, not from api.models.package +from eatery.models import EateryStore + from .AlertModel import AlertStore -from .EateryModel import EateryStore -from .EventScheduleModel import ( - EventSchedule, - RepeatingEventSchedule, - ScheduleException, -) +from .EventScheduleModel import EventSchedule, RepeatingEventSchedule, ScheduleException from .MenuModel import ( - MenuStore, + CategoryItemAssociation, CategoryStore, ItemStore, + MenuStore, SubItemStore, - CategoryItemAssociation, ) from .ReportModel import ReportStore from .TransactionModel import TransactionHistoryStore diff --git a/src/api/serializers.py b/src/api/serializers.py index 92d4274..8fc5ff8 100644 --- a/src/api/serializers.py +++ b/src/api/serializers.py @@ -3,12 +3,6 @@ import api.models as models -class EateryStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.EateryStore - fields = "__all__" - - class AlertStoreSerializer(serializers.ModelSerializer): class Meta: model = models.AlertStore @@ -50,6 +44,7 @@ class Meta: model = models.RepeatingEventSchedule fields = "__all__" + class ScheduleExceptionSerializer(serializers.ModelSerializer): class Meta: model = models.ScheduleException diff --git a/src/api/urls.py b/src/api/urls.py index a5e8d4f..5472297 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -1,9 +1,10 @@ from django.urls import path +from eatery.views import UpdateView -from api.views import MainDfgView, UpdateView, ReportView +from api.views import MainDfgView, ReportView urlpatterns = [ path("", MainDfgView.as_view(), name="main"), path("update", UpdateView.as_view(), name="update"), - path("report", ReportView.as_view(), name="report") + path("report", ReportView.as_view(), name="report"), ] diff --git a/src/api/util/json.py b/src/api/util/json.py index 51f01a0..ecea0a6 100644 --- a/src/api/util/json.py +++ b/src/api/util/json.py @@ -26,6 +26,10 @@ def verify_json_fields( if not isinstance(json[field], int) or EateryID(json[field]) == None: return False + for field in json: + if field not in field_type_map and field not in nullable: + return False + return True diff --git a/src/api/views.py b/src/api/views.py index ccb4dc2..cfcc0b3 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -6,7 +6,6 @@ from rest_framework.views import APIView from api.controllers.create_report import CreateReportController -from api.controllers.update_eatery import UpdateEateryController from api.datatype.Eatery import EateryID from api.dfg.main import main_dfg from api.util.json import FieldType, error_json, success_json, verify_json_fields @@ -50,23 +49,3 @@ def post(self, request): eatery_id=EateryID(id_provided) if id_provided else None, ).process() return JsonResponse(success_json("Reported")) - - -class UpdateView(APIView): - def post(self, request): - text_params = request.POST - try: - image_param = request.FILES.get("image") - except: - image_param = None - - try: - id = int(text_params.get("id")) - except: - return JsonResponse(error_json("ID must be castable to an int")) - - try: - UpdateEateryController(EateryID(id), text_params, image_param).process() - return JsonResponse(success_json("Updated")) - except Exception as e: - return JsonResponse(error_json(str(e))) diff --git a/src/eatery/__init__.py b/src/eatery/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/eatery/admin.py b/src/eatery/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/src/eatery/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/eatery/apps.py b/src/eatery/apps.py new file mode 100644 index 0000000..74d7a00 --- /dev/null +++ b/src/eatery/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EateryConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "eatery" diff --git a/src/api/controllers/update_eatery.py b/src/eatery/controllers/update_eatery.py similarity index 79% rename from src/api/controllers/update_eatery.py rename to src/eatery/controllers/update_eatery.py index 8a8d4c4..578ed2a 100644 --- a/src/api/controllers/update_eatery.py +++ b/src/eatery/controllers/update_eatery.py @@ -1,10 +1,11 @@ -from django.http import QueryDict -from api.datatype.Eatery import EateryID -from api.models import EateryStore import base64 -import requests import os +import requests +from api.datatype.Eatery import EateryID +from django.http import QueryDict +from eatery.models import EateryStore + class UpdateEateryController: def __init__(self, id: EateryID, update_map: QueryDict, image): @@ -15,7 +16,6 @@ def __init__(self, id: EateryID, update_map: QueryDict, image): Requires: id is a valid id and all keys in update_map are valid fields in the EateryStore class (except username/password cannot be provided), as well as an optional image field containing an image file to be uploaded - Raises: Exception when invalid keys are provided """ self.id = id self.update_data = {} @@ -24,27 +24,12 @@ def __init__(self, id: EateryID, update_map: QueryDict, image): img_url = self.upload_image(image) self.update_data["image_url"] = img_url - allowed_fields = [ - "id", - "name", - "menu_summary", - "location", - "campus_area", - "online_order_url", - "latitude", - "longitude", - "payment_accepts_meal_swipes", - "payment_accepts_brbs", - "payment_accepts_cash", - ] - + # Query dict is immutable, so need to do this to remove id + to_remove = ["id"] + self.update_data = {} for key, val in update_map.items(): - try: - if key not in allowed_fields: - raise Exception + if key not in to_remove: self.update_data[key] = val - except: - raise Exception("Invalid update field(s) provided") def upload_image(self, image): """ diff --git a/src/eatery/migrations/0001_initial.py b/src/eatery/migrations/0001_initial.py new file mode 100644 index 0000000..cf6b3cb --- /dev/null +++ b/src/eatery/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 4.0 on 2022-04-23 22:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='EateryStore', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('name', models.CharField(blank=True, max_length=40)), + ('menu_summary', models.CharField(blank=True, max_length=60)), + ('image_url', models.URLField(blank=True)), + ('location', models.CharField(blank=True, max_length=30)), + ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), + ('online_order_url', models.URLField(blank=True)), + ('latitude', models.FloatField(blank=True, null=True)), + ('longitude', models.FloatField(blank=True, null=True)), + ('payment_accepts_meal_swipes', models.BooleanField(blank=True, null=True)), + ('payment_accepts_brbs', models.BooleanField(blank=True, null=True)), + ('payment_accepts_cash', models.BooleanField(blank=True, null=True)), + ], + ), + ] diff --git a/src/eatery/migrations/__init__.py b/src/eatery/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/eatery/models.py b/src/eatery/models.py new file mode 100644 index 0000000..3d4c2ca --- /dev/null +++ b/src/eatery/models.py @@ -0,0 +1,25 @@ +from django.db import models + + +class EateryStore(models.Model): + class CampusArea(models.TextChoices): + WEST = "West" + NORTH = "North" + CENTRAL = "Central" + COLLEGETOWN = "Collegetown" + NONE = "" + + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=40, blank=True) + menu_summary = models.CharField(max_length=60, blank=True) + image_url = models.URLField(blank=True) + location = models.CharField(max_length=30, blank=True) + campus_area = models.CharField( + max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True + ) + online_order_url = models.URLField(blank=True) + latitude = models.FloatField(null=True, blank=True) + longitude = models.FloatField(null=True, blank=True) + payment_accepts_meal_swipes = models.BooleanField(null=True, blank=True) + payment_accepts_brbs = models.BooleanField(null=True, blank=True) + payment_accepts_cash = models.BooleanField(null=True, blank=True) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py new file mode 100644 index 0000000..49e24bb --- /dev/null +++ b/src/eatery/serializers.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +import eatery.models as models + + +class EateryStoreSerializer(serializers.ModelSerializer): + class Meta: + model = models.EateryStore + fields = "__all__" diff --git a/src/eatery/tests.py b/src/eatery/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/eatery/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/eatery/views.py b/src/eatery/views.py new file mode 100644 index 0000000..3ee24db --- /dev/null +++ b/src/eatery/views.py @@ -0,0 +1,45 @@ +from api.datatype.Eatery import EateryID +from api.util.json import (FieldType, error_json, success_json, + verify_json_fields) +from django.http import JsonResponse +from django.shortcuts import render +from rest_framework.views import APIView + +from .controllers.update_eatery import UpdateEateryController + + +class UpdateView(APIView): + def post(self, request): + text_params = request.POST + if not verify_json_fields( + text_params, + { + "id": FieldType.STRING, + }, + [ + "name", + "menu_summary", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + "image", + ], + ): + return JsonResponse(error_json("Malformed Request")) + + id = int(text_params.get("id")) + try: + image_param = request.FILES.get("image") + except: + image_param = None + + try: + UpdateEateryController(EateryID(id), text_params, image_param).process() + return JsonResponse(success_json("Updated")) + except Exception as e: + return JsonResponse(error_json(str(e))) diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index b6edd5b..8f5bf29 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -10,8 +10,8 @@ https://docs.djangoproject.com/en/4.0/ref/settings/ """ -from pathlib import Path import os +from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -39,6 +39,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "api", + "eatery", "rest_framework", ] From 35e9b81ef30e0dae7933bbe6b387553134f35581 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sat, 23 Apr 2022 18:48:50 -0400 Subject: [PATCH 087/305] Move eatery datatypes --- src/api/controllers/create_report.py | 2 +- src/api/dfg/nodes/CornellDiningNow.py | 23 ++++-- src/api/dfg/nodes/EateriesFromDB.py | 4 +- src/api/dfg/nodes/EateryStubs.py | 3 +- src/api/dfg/nodes/macros/EateryEvents.py | 2 +- .../dfg/nodes/schedule/CacheMenuInjection.py | 2 +- src/api/dfg/nodes/schedule/ClosedSchedule.py | 2 +- .../dfg/nodes/schedule/CornellDiningEvents.py | 2 +- .../dfg/nodes/schedule/ModifiedSchedules.py | 2 +- .../dfg/nodes/schedule/RepeatingSchedule.py | 2 +- src/api/dfg/nodes/system/ConvertFromJson.py | 18 ++--- src/api/dfg/nodes/system/ConvertToJson.py | 13 ++-- src/api/dfg/nodes/system/EateryGenerator.py | 23 +++--- src/api/dfg/nodes/system/Mapping.py | 7 +- src/api/dfg/nodes/wait_times/WaitTimes.py | 76 ++++++++++++------- src/api/util/constants.py | 2 +- src/api/util/json.py | 2 +- src/api/views.py | 2 +- src/eatery/controllers/update_eatery.py | 2 +- src/{api => eatery}/datatype/Eatery.py | 5 +- src/{api => eatery}/datatype/EateryAlert.py | 0 src/eatery/views.py | 6 +- 22 files changed, 112 insertions(+), 88 deletions(-) rename src/{api => eatery}/datatype/Eatery.py (99%) rename src/{api => eatery}/datatype/EateryAlert.py (100%) diff --git a/src/api/controllers/create_report.py b/src/api/controllers/create_report.py index 16addf5..b78dabf 100644 --- a/src/api/controllers/create_report.py +++ b/src/api/controllers/create_report.py @@ -1,8 +1,8 @@ from datetime import datetime from typing import Optional -from api.datatype.Eatery import EateryID from api.models import ReportStore +from eatery.datatype.Eatery import EateryID class CreateReportController: diff --git a/src/api/dfg/nodes/CornellDiningNow.py b/src/api/dfg/nodes/CornellDiningNow.py index 7f1e067..98693c7 100644 --- a/src/api/dfg/nodes/CornellDiningNow.py +++ b/src/api/dfg/nodes/CornellDiningNow.py @@ -1,11 +1,10 @@ import requests - from api.dfg.nodes.DfgNode import DfgNode -from api.datatype.Eatery import Eatery -from api.util.constants import dining_id_to_internal_id, CORNELL_DINING_URL +from api.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id +from eatery.datatype.Eatery import Eatery -class CornellDiningNow(DfgNode): +class CornellDiningNow(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: try: response = requests.get(CORNELL_DINING_URL).json() @@ -33,10 +32,20 @@ def parse_eatery(json_eatery: dict) -> Eatery: latitude=json_eatery["latitude"], longitude=json_eatery["longitude"], payment_accepts_cash=True, - payment_accepts_brbs=any([method["descrshort"] == "Meal Plan - Debit" for method in json_eatery["payMethods"]]), - payment_accepts_meal_swipes=any([method["descrshort"] == "Meal Plan - Swipe" for method in json_eatery["payMethods"]]), + payment_accepts_brbs=any( + [ + method["descrshort"] == "Meal Plan - Debit" + for method in json_eatery["payMethods"] + ] + ), + payment_accepts_meal_swipes=any( + [ + method["descrshort"] == "Meal Plan - Swipe" + for method in json_eatery["payMethods"] + ] + ), location=json_eatery["location"], - online_order_url=json_eatery["onlineOrderUrl"] + online_order_url=json_eatery["onlineOrderUrl"], ) def description(self): diff --git a/src/api/dfg/nodes/EateriesFromDB.py b/src/api/dfg/nodes/EateriesFromDB.py index 51a7633..f9b28c7 100644 --- a/src/api/dfg/nodes/EateriesFromDB.py +++ b/src/api/dfg/nodes/EateriesFromDB.py @@ -1,10 +1,10 @@ from datetime import datetime -from api.datatype.Eatery import Eatery, EateryID -from api.datatype.EateryAlert import EateryAlert from api.dfg.nodes.DfgNode import DfgNode from api.models import AlertStore from api.serializers import AlertStoreSerializer +from eatery.datatype.Eatery import Eatery, EateryID +from eatery.datatype.EateryAlert import EateryAlert from eatery.models import EateryStore from eatery.serializers import EateryStoreSerializer diff --git a/src/api/dfg/nodes/EateryStubs.py b/src/api/dfg/nodes/EateryStubs.py index 59a9a08..780f15c 100644 --- a/src/api/dfg/nodes/EateryStubs.py +++ b/src/api/dfg/nodes/EateryStubs.py @@ -1,9 +1,8 @@ from api.dfg.nodes.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID +from eatery.datatype.Eatery import Eatery, EateryID class EateryStubs(DfgNode): - def __call__(self, *args, **kwargs) -> list[Eatery]: return [Eatery(id=id) for id in EateryID] diff --git a/src/api/dfg/nodes/macros/EateryEvents.py b/src/api/dfg/nodes/macros/EateryEvents.py index fdd5165..0bab2d5 100644 --- a/src/api/dfg/nodes/macros/EateryEvents.py +++ b/src/api/dfg/nodes/macros/EateryEvents.py @@ -1,4 +1,3 @@ -from api.datatype.Eatery import EateryID from api.dfg.nodes.DfgNode import DfgNode from api.dfg.nodes.macros.LeftMergeRegularEvents import LeftMergeRegularEvents from api.dfg.nodes.macros.LeftMergeRepeatedEvents import LeftMergeRepeatedEvents @@ -7,6 +6,7 @@ from api.dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents from api.dfg.nodes.schedule.ModifiedSchedules import ModifiedSchedules from api.dfg.nodes.schedule.RepeatingSchedule import RepeatingSchedule +from eatery.datatype.Eatery import EateryID # Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if diff --git a/src/api/dfg/nodes/schedule/CacheMenuInjection.py b/src/api/dfg/nodes/schedule/CacheMenuInjection.py index 9351471..83b3384 100644 --- a/src/api/dfg/nodes/schedule/CacheMenuInjection.py +++ b/src/api/dfg/nodes/schedule/CacheMenuInjection.py @@ -1,4 +1,3 @@ -from api.datatype.Eatery import Eatery, EateryID from api.datatype.Menu import Menu from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem @@ -6,6 +5,7 @@ from api.datatype.MenuSubItem import MenuSubItem from api.dfg.nodes.DfgNode import DfgNode from api.models import CategoryItemAssociation, SubItemStore +from eatery.datatype.Eatery import Eatery, EateryID class CacheMenuInjection(DfgNode): diff --git a/src/api/dfg/nodes/schedule/ClosedSchedule.py b/src/api/dfg/nodes/schedule/ClosedSchedule.py index 963553b..d584585 100644 --- a/src/api/dfg/nodes/schedule/ClosedSchedule.py +++ b/src/api/dfg/nodes/schedule/ClosedSchedule.py @@ -1,6 +1,6 @@ -from api.datatype.Eatery import Eatery, EateryID from api.dfg.nodes.DfgNode import DfgNode from api.models.EventScheduleModel import ExceptionType, ScheduleException +from eatery.datatype.Eatery import Eatery, EateryID class ClosedSchedule(DfgNode): diff --git a/src/api/dfg/nodes/schedule/CornellDiningEvents.py b/src/api/dfg/nodes/schedule/CornellDiningEvents.py index ef84211..c6cf812 100644 --- a/src/api/dfg/nodes/schedule/CornellDiningEvents.py +++ b/src/api/dfg/nodes/schedule/CornellDiningEvents.py @@ -1,13 +1,13 @@ from datetime import date import requests -from api.datatype.Eatery import Eatery, EateryID from api.datatype.Event import Event from api.datatype.Menu import Menu from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem from api.dfg.nodes.DfgNode import DfgNode from api.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id +from eatery.datatype.Eatery import Eatery, EateryID class CornellDiningEvents(DfgNode): diff --git a/src/api/dfg/nodes/schedule/ModifiedSchedules.py b/src/api/dfg/nodes/schedule/ModifiedSchedules.py index 1369ca8..12b6a2c 100644 --- a/src/api/dfg/nodes/schedule/ModifiedSchedules.py +++ b/src/api/dfg/nodes/schedule/ModifiedSchedules.py @@ -1,10 +1,10 @@ from datetime import timedelta -from api.datatype.Eatery import Eatery, EateryID from api.datatype.Event import Event from api.dfg.nodes.DfgNode import DfgNode from api.models.EventScheduleModel import ExceptionType, ScheduleException from api.util.time import combined_timestamp +from eatery.datatype.Eatery import Eatery, EateryID class ModifiedSchedules(DfgNode): diff --git a/src/api/dfg/nodes/schedule/RepeatingSchedule.py b/src/api/dfg/nodes/schedule/RepeatingSchedule.py index 6d8e3b7..5cbfcb0 100644 --- a/src/api/dfg/nodes/schedule/RepeatingSchedule.py +++ b/src/api/dfg/nodes/schedule/RepeatingSchedule.py @@ -1,10 +1,10 @@ from datetime import timedelta -from api.datatype.Eatery import Eatery, EateryID from api.datatype.Event import Event from api.dfg.nodes.DfgNode import DfgNode from api.models import RepeatingEventSchedule from api.util.time import combined_timestamp +from eatery.datatype.Eatery import Eatery, EateryID class RepeatingSchedule(DfgNode): diff --git a/src/api/dfg/nodes/system/ConvertFromJson.py b/src/api/dfg/nodes/system/ConvertFromJson.py index ba340a5..5ac993f 100644 --- a/src/api/dfg/nodes/system/ConvertFromJson.py +++ b/src/api/dfg/nodes/system/ConvertFromJson.py @@ -1,11 +1,11 @@ from typing import Union -from api.dfg.nodes.DfgNode import DfgNode -from api.datatype.Eatery import Eatery from api.datatype.Event import Event +from api.dfg.nodes.DfgNode import DfgNode +from eatery.datatype.Eatery import Eatery -class EateryFromJson(DfgNode): +class EateryFromJson(DfgNode): def __init__(self, child: DfgNode): self.child = child @@ -19,18 +19,15 @@ def children(self): @staticmethod def from_json(obj: Union[list, dict], *args, **kwargs): if isinstance(obj, list): - return [ - EateryFromJson.from_json(elem, *args, **kwargs) - for elem in obj - ] + return [EateryFromJson.from_json(elem, *args, **kwargs) for elem in obj] else: return Eatery.from_json(obj) def description(self): return "EateryFromJson" -class EventFromJson(DfgNode): +class EventFromJson(DfgNode): def __init__(self, child: DfgNode): self.child = child @@ -44,10 +41,7 @@ def children(self): @staticmethod def from_json(obj: Union[list, dict], *args, **kwargs): if isinstance(obj, list): - return [ - EventFromJson.from_json(elem, *args, **kwargs) - for elem in obj - ] + return [EventFromJson.from_json(elem, *args, **kwargs) for elem in obj] else: return Event.from_json(obj) diff --git a/src/api/dfg/nodes/system/ConvertToJson.py b/src/api/dfg/nodes/system/ConvertToJson.py index c364035..eab2214 100644 --- a/src/api/dfg/nodes/system/ConvertToJson.py +++ b/src/api/dfg/nodes/system/ConvertToJson.py @@ -1,11 +1,11 @@ from typing import Union -from api.datatype.Event import Event +from api.datatype.Event import Event from api.dfg.nodes.DfgNode import DfgNode -from api.datatype.Eatery import Eatery +from eatery.datatype.Eatery import Eatery -class ConvertToJson(DfgNode): +class ConvertToJson(DfgNode): def __init__(self, child: DfgNode): self.child = child @@ -19,15 +19,12 @@ def children(self): @staticmethod def to_json(obj: Union[list, Eatery, Event], *args, **kwargs): if isinstance(obj, list): - return [ - ConvertToJson.to_json(elem, *args, **kwargs) - for elem in obj - ] + return [ConvertToJson.to_json(elem, *args, **kwargs) for elem in obj] else: return obj.to_json( tzinfo=kwargs.get("tzinfo"), start=kwargs.get("start"), - end=kwargs.get("end") + end=kwargs.get("end"), ) def description(self): diff --git a/src/api/dfg/nodes/system/EateryGenerator.py b/src/api/dfg/nodes/system/EateryGenerator.py index e1b3d7a..0e98995 100644 --- a/src/api/dfg/nodes/system/EateryGenerator.py +++ b/src/api/dfg/nodes/system/EateryGenerator.py @@ -1,14 +1,15 @@ -from api.dfg.nodes.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID from typing import Optional -class EateryGenerator(DfgNode): +from api.dfg.nodes.DfgNode import DfgNode +from eatery.datatype.Eatery import Eatery, EateryID + +class EateryGenerator(DfgNode): def __init__( - self, + self, eatery_id: EateryID, - events_dfg: Optional[DfgNode] = None, - wait_times_dfg: Optional[DfgNode] = None + events_dfg: Optional[DfgNode] = None, + wait_times_dfg: Optional[DfgNode] = None, ): self.eatery_id = eatery_id self.events_dfg = events_dfg @@ -16,9 +17,13 @@ def __init__( def __call__(self, *args, **kwargs) -> list: return Eatery( - id = self.eatery_id, - events=None if self.events_dfg is None else self.events_dfg(*args, **kwargs), - wait_times=None if self.wait_times_dfg is None else self.wait_times_dfg(*args, **kwargs) + id=self.eatery_id, + events=None + if self.events_dfg is None + else self.events_dfg(*args, **kwargs), + wait_times=None + if self.wait_times_dfg is None + else self.wait_times_dfg(*args, **kwargs), ) def description(self): diff --git a/src/api/dfg/nodes/system/Mapping.py b/src/api/dfg/nodes/system/Mapping.py index 565839c..8028549 100644 --- a/src/api/dfg/nodes/system/Mapping.py +++ b/src/api/dfg/nodes/system/Mapping.py @@ -1,9 +1,10 @@ +from typing import Any, Callable + from api.dfg.nodes.DfgNode import DfgNode -from api.datatype.Eatery import Eatery, EateryID -from typing import Callable, Any +from eatery.datatype.Eatery import Eatery, EateryID -class Mapping(DfgNode): +class Mapping(DfgNode): def __init__(self, child: DfgNode, fn: Callable[[Any, dict], DfgNode]): self.child = child self.fn = fn diff --git a/src/api/dfg/nodes/wait_times/WaitTimes.py b/src/api/dfg/nodes/wait_times/WaitTimes.py index a2470da..6279612 100644 --- a/src/api/dfg/nodes/wait_times/WaitTimes.py +++ b/src/api/dfg/nodes/wait_times/WaitTimes.py @@ -1,17 +1,17 @@ from datetime import date, timedelta -from django.db.models import Avg -import pytz -from api.datatype.Eatery import Eatery, EateryID +import pytz from api.datatype.Event import Event from api.datatype.WaitTime import WaitTime from api.datatype.WaitTimesDay import WaitTimesDay from api.dfg.nodes.DfgNode import DfgNode from api.models import TransactionHistoryStore from api.util.time import combined_timestamp +from django.db.models import Avg +from eatery.datatype.Eatery import Eatery, EateryID -class WaitTimes(DfgNode): +class WaitTimes(DfgNode): def __init__(self, eatery_id: EateryID, cache): self.eatery_id = eatery_id self.cache = cache @@ -24,19 +24,27 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: transactions[date] = [] past_days = [] for i in range(1, 13): - past_days.append(date - timedelta(days=7*i)) - transaction_avg_counts = TransactionHistoryStore.objects.filter(canonical_date__in=past_days) \ - .values("eatery_id", "block_end_time") \ + past_days.append(date - timedelta(days=7 * i)) + transaction_avg_counts = ( + TransactionHistoryStore.objects.filter(canonical_date__in=past_days) + .values("eatery_id", "block_end_time") .annotate(transaction_avg=Avg("transaction_count")) + ) for unit in transaction_avg_counts: transactions[date].append(unit) date += timedelta(days=1) self.cache["transactions"] = transactions - + eatery_wait_times = [] for date in self.cache["transactions"]: - eatery_transaction_avgs = [transaction_avg for transaction_avg in self.cache["transactions"][date] if transaction_avg["eatery_id"] == self.eatery_id.value] - date_wait_times = WaitTimes.generate_eatery_wait_times_by_day(self.eatery_id, date, eatery_transaction_avgs, kwargs.get("tzinfo")) + eatery_transaction_avgs = [ + transaction_avg + for transaction_avg in self.cache["transactions"][date] + if transaction_avg["eatery_id"] == self.eatery_id.value + ] + date_wait_times = WaitTimes.generate_eatery_wait_times_by_day( + self.eatery_id, date, eatery_transaction_avgs, kwargs.get("tzinfo") + ) if date_wait_times is not None: eatery_wait_times.append(date_wait_times) @@ -74,10 +82,7 @@ def base_time_to_get_food(eatery_id: EateryID) -> list[int]: @staticmethod def generate_eatery_wait_times_by_day( - eatery_id: EateryID, - date: date, - transactions: list, - tzinfo: pytz.tzinfo + eatery_id: EateryID, date: date, transactions: list, tzinfo: pytz.tzinfo ) -> WaitTimesDay: wait_times_data = [] customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] @@ -85,31 +90,46 @@ def generate_eatery_wait_times_by_day( base_times = WaitTimes.base_time_to_get_food(eatery_id) line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago - how_long_ago_guest_arrival = base_times[1] + line_decrease_times[1] * transactions[index]["transaction_avg"] + how_long_ago_guest_arrival = ( + base_times[1] + + line_decrease_times[1] * transactions[index]["transaction_avg"] + ) prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) if prev_bucket_guest_arrival > 9: pass # TODO: Send a slack error here instead # print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") else: - customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[index]["transaction_avg"] + customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[ + index + ]["transaction_avg"] num_customers = customers_waiting_in_line.pop(0) - wait_time_low = int(base_times[0] + line_decrease_times[0] * num_customers) - wait_time_expected = int(base_times[1] + line_decrease_times[1] * num_customers) - wait_time_high = int(base_times[2] + line_decrease_times[2] * num_customers) + wait_time_low = int( + base_times[0] + line_decrease_times[0] * num_customers + ) + wait_time_expected = int( + base_times[1] + line_decrease_times[1] * num_customers + ) + wait_time_high = int( + base_times[2] + line_decrease_times[2] * num_customers + ) customers_waiting_in_line.append(0.0) - block_end_time = transactions[index]['block_end_time'] - timestamp = int(combined_timestamp(date, block_end_time, tzinfo) - 5 * 60 / 2) - wait_times_data.insert(0, WaitTime( - timestamp=timestamp, - wait_time_low=wait_time_low, - wait_time_expected=wait_time_expected, - wait_time_high=wait_time_high - )) + block_end_time = transactions[index]["block_end_time"] + timestamp = int( + combined_timestamp(date, block_end_time, tzinfo) - 5 * 60 / 2 + ) + wait_times_data.insert( + 0, + WaitTime( + timestamp=timestamp, + wait_time_low=wait_time_low, + wait_time_expected=wait_time_expected, + wait_time_high=wait_time_high, + ), + ) return WaitTimesDay(canonical_date=date, data=wait_times_data) - def description(self): return "WaitTimes" diff --git a/src/api/util/constants.py b/src/api/util/constants.py index fa5cbe6..5e09f25 100644 --- a/src/api/util/constants.py +++ b/src/api/util/constants.py @@ -1,6 +1,6 @@ from enum import Enum -from api.datatype.Eatery import EateryID +from eatery.datatype.Eatery import EateryID CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" diff --git a/src/api/util/json.py b/src/api/util/json.py index ecea0a6..a7d80af 100644 --- a/src/api/util/json.py +++ b/src/api/util/json.py @@ -1,7 +1,7 @@ from enum import Enum from typing import List, Mapping, Optional -from api.datatype.Eatery import EateryID +from eatery.datatype.Eatery import EateryID class FieldType(Enum): diff --git a/src/api/views.py b/src/api/views.py index cfcc0b3..6d3ee65 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -3,10 +3,10 @@ import pytz from django.http import JsonResponse +from eatery.datatype.Eatery import EateryID from rest_framework.views import APIView from api.controllers.create_report import CreateReportController -from api.datatype.Eatery import EateryID from api.dfg.main import main_dfg from api.util.json import FieldType, error_json, success_json, verify_json_fields diff --git a/src/eatery/controllers/update_eatery.py b/src/eatery/controllers/update_eatery.py index 578ed2a..b8f68b8 100644 --- a/src/eatery/controllers/update_eatery.py +++ b/src/eatery/controllers/update_eatery.py @@ -2,8 +2,8 @@ import os import requests -from api.datatype.Eatery import EateryID from django.http import QueryDict +from eatery.datatype.Eatery import EateryID from eatery.models import EateryStore diff --git a/src/api/datatype/Eatery.py b/src/eatery/datatype/Eatery.py similarity index 99% rename from src/api/datatype/Eatery.py rename to src/eatery/datatype/Eatery.py index d2a827a..95c979b 100644 --- a/src/api/datatype/Eatery.py +++ b/src/eatery/datatype/Eatery.py @@ -1,12 +1,11 @@ from datetime import date -from typing import Optional from enum import Enum +from typing import Optional import pytz -from api.datatype.EateryAlert import EateryAlert - from api.datatype.Event import Event, filter_range from api.datatype.WaitTimesDay import WaitTimesDay +from eatery.datatype.EateryAlert import EateryAlert class EateryID(Enum): diff --git a/src/api/datatype/EateryAlert.py b/src/eatery/datatype/EateryAlert.py similarity index 100% rename from src/api/datatype/EateryAlert.py rename to src/eatery/datatype/EateryAlert.py diff --git a/src/eatery/views.py b/src/eatery/views.py index 3ee24db..8bd3abe 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -1,10 +1,10 @@ -from api.datatype.Eatery import EateryID -from api.util.json import (FieldType, error_json, success_json, - verify_json_fields) +from api.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse from django.shortcuts import render from rest_framework.views import APIView +from eatery.datatype.Eatery import EateryID + from .controllers.update_eatery import UpdateEateryController From 0cbfef1f805f2e80add0d661a2437b50eb713645 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sat, 23 Apr 2022 20:35:33 -0400 Subject: [PATCH 088/305] Updated admin registering --- src/api/admin.py | 2 -- src/eatery/admin.py | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/api/admin.py b/src/api/admin.py index e519476..1a5cfa7 100644 --- a/src/api/admin.py +++ b/src/api/admin.py @@ -1,11 +1,9 @@ -import eatery.models as eatery_models from django.contrib import admin import api.models as models # Register your models here. -admin.site.register(eatery_models.EateryStore) admin.site.register(models.MenuStore) admin.site.register(models.ItemStore) admin.site.register(models.SubItemStore) diff --git a/src/eatery/admin.py b/src/eatery/admin.py index 8c38f3f..6c379a7 100644 --- a/src/eatery/admin.py +++ b/src/eatery/admin.py @@ -1,3 +1,7 @@ from django.contrib import admin +import eatery.models as models + # Register your models here. + +admin.site.register(models.EateryStore) From 3b68cd4e78cceb564c9de432f9708d374cbbd0b5 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Wed, 27 Apr 2022 17:49:06 -0400 Subject: [PATCH 089/305] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index bbf54d2..3ed2ff6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ # eatery-blue-backend This is the backend for eatery-blue-backend. + +SP22 Members +-------------- +- Marya Kim +- Archit Mehta From 026d9383cde9723af18e58b5a21a18387129cbff Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Wed, 27 Apr 2022 18:31:04 -0400 Subject: [PATCH 090/305] Update Dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0086eab..7535b63 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,4 +4,5 @@ RUN mkdir /usr/app WORKDIR /usr/app COPY ./src . COPY ./requirements.txt . -RUN pip install -r requirements.txt \ No newline at end of file +RUN pip install -r requirements.txt +CMD python3 manage.py runserver From 124f13a494949532a9e28a536e917f392a1b3c60 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Wed, 27 Apr 2022 19:48:25 -0400 Subject: [PATCH 091/305] Change environment variables and install missing packages --- .envrctemplate | 9 +++++++++ requirements.txt | 1 + src/eatery_blue_backend/settings.py | 18 +++++++++--------- 3 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 .envrctemplate diff --git a/.envrctemplate b/.envrctemplate new file mode 100644 index 0000000..c1017d1 --- /dev/null +++ b/.envrctemplate @@ -0,0 +1,9 @@ +export CORNELL_VENDOR_TOKEN= +export CORNELL_VENDOR_API_KEY= +export DJANGO_ALLOWED_HOSTS= +export IS_PROD= +export POSTGRES_NAME= +export POSTGRES_USER= +export POSTGRES_PASSWORD= +export POSTGRES_HOST= +export POSTGRES_PORT= diff --git a/requirements.txt b/requirements.txt index a0381be..d86038e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ oauthlib==3.1.1 pathspec==0.9.0 platformdirs==2.4.0 protobuf==3.19.1 +psycopg2-binary==2.9.3 pyasn1==0.4.8 pyasn1-modules==0.2.8 pyparsing==3.0.6 diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index b6edd5b..33c1275 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -76,16 +76,9 @@ # Database # https://docs.djangoproject.com/en/4.0/ref/settings/#databases -IS_LOCAL = os.getenv("LOCAL") +IS_PROD = os.getenv("IS_PROD") == "True" -if IS_LOCAL == "True": - DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", - } - } -elif IS_LOCAL == "False": +if IS_PROD: DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql_psycopg2", @@ -96,6 +89,13 @@ "PORT": os.getenv("POSTGRES_PORT"), } } +else: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } + } # Password validation From 9ed24dfdf06e6bf55b6b9398da2470232a231c8a Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Wed, 27 Apr 2022 19:59:34 -0400 Subject: [PATCH 092/305] Update Dockerfile --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7535b63..7208209 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,3 @@ WORKDIR /usr/app COPY ./src . COPY ./requirements.txt . RUN pip install -r requirements.txt -CMD python3 manage.py runserver From 11e74d98baffd4714cb8013f9a99beb4ea446bbc Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Wed, 27 Apr 2022 20:05:36 -0400 Subject: [PATCH 093/305] Update Dockerfile --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 7208209..514ea27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,3 +5,4 @@ WORKDIR /usr/app COPY ./src . COPY ./requirements.txt . RUN pip install -r requirements.txt +EXPOSE 8000 From 04b55a692263fc190de045751c3b00c64f12e6ed Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Mon, 18 Apr 2022 20:49:00 -0400 Subject: [PATCH 094/305] Make eatery id optional in reports --- src/api/controllers/create_report.py | 15 ++++++++------- .../0012_alter_reportstore_eatery.py | 19 +++++++++++++++++++ src/api/models/ReportModel.py | 7 ++++--- src/api/views.py | 18 ++++++++++-------- 4 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 src/api/migrations/0012_alter_reportstore_eatery.py diff --git a/src/api/controllers/create_report.py b/src/api/controllers/create_report.py index b0e026f..16addf5 100644 --- a/src/api/controllers/create_report.py +++ b/src/api/controllers/create_report.py @@ -1,10 +1,12 @@ from datetime import datetime +from typing import Optional + from api.datatype.Eatery import EateryID from api.models import ReportStore -class CreateReportController: - def __init__(self, eatery_id: EateryID, type: str, content: str): +class CreateReportController: + def __init__(self, type: str, content: str, eatery_id: Optional[EateryID] = None): self.eatery_id = eatery_id self.type = type self.content = content @@ -12,9 +14,8 @@ def __init__(self, eatery_id: EateryID, type: str, content: str): def process(self): current_timestamp = datetime.now().timestamp() ReportStore.objects.create( - eatery_id = self.eatery_id.value, - type = self.type, - content = self.content, - created_timestamp = current_timestamp + eatery_id=self.eatery_id.value if self.eatery_id else None, + type=self.type, + content=self.content, + created_timestamp=current_timestamp, ) - \ No newline at end of file diff --git a/src/api/migrations/0012_alter_reportstore_eatery.py b/src/api/migrations/0012_alter_reportstore_eatery.py new file mode 100644 index 0000000..f6a6ccf --- /dev/null +++ b/src/api/migrations/0012_alter_reportstore_eatery.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0 on 2022-04-19 00:44 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0011_scheduleexception_eatery'), + ] + + operations = [ + migrations.AlterField( + model_name='reportstore', + name='eatery', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore'), + ), + ] diff --git a/src/api/models/ReportModel.py b/src/api/models/ReportModel.py index eea6e24..f28aaeb 100644 --- a/src/api/models/ReportModel.py +++ b/src/api/models/ReportModel.py @@ -1,8 +1,9 @@ -from django.db import models from api.models.EateryModel import EateryStore +from django.db import models + class ReportStore(models.Model): - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING, null=True) type = models.CharField(max_length=200) content = models.TextField() - created_timestamp = models.IntegerField() \ No newline at end of file + created_timestamp = models.IntegerField() diff --git a/src/api/views.py b/src/api/views.py index 48b3f9d..95e23bb 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -1,14 +1,15 @@ -from django.http import JsonResponse -from rest_framework.views import APIView +import json from datetime import date, timedelta + import pytz -import json +from django.http import JsonResponse +from rest_framework.views import APIView -from api.datatype.Eatery import EateryID -from api.dfg.main import main_dfg from api.controllers.create_report import CreateReportController from api.controllers.update_eatery import UpdateEateryController -from api.util.json import verify_json_fields, success_json, error_json, FieldType +from api.datatype.Eatery import EateryID +from api.dfg.main import main_dfg +from api.util.json import FieldType, error_json, success_json, verify_json_fields # Create your views here. @@ -34,16 +35,17 @@ def post(self, request): if not verify_json_fields( json_body, { - "eatery_id": FieldType.INT, "type": FieldType.STRING, "content": FieldType.STRING, }, ): return JsonResponse(error_json("Malformed Request")) + + id_provided = json_body.get("eatery_id") CreateReportController( - eatery_id=EateryID(json_body["eatery_id"]), type=json_body["type"], content=json_body["content"], + eatery_id=EateryID(id_provided) if id_provided else None, ).process() return JsonResponse(success_json("Reported")) From 4957e4acf96509cf3503c2354e0bcb4acb0db9e8 Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Mon, 18 Apr 2022 21:02:30 -0400 Subject: [PATCH 095/305] Edit defensive checks --- src/api/util/json.py | 31 ++++++++++++++----------------- src/api/views.py | 2 ++ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/api/util/json.py b/src/api/util/json.py index aec6d35..51f01a0 100644 --- a/src/api/util/json.py +++ b/src/api/util/json.py @@ -1,18 +1,21 @@ -from typing import Mapping from enum import Enum +from typing import List, Mapping, Optional from api.datatype.Eatery import EateryID + class FieldType(Enum): - INT = 'int' - STRING = 'str' + INT = "int" + STRING = "str" EATERYID = "eateryid" - -def verify_json_fields(json, field_type_map: Mapping[str, FieldType]): + +def verify_json_fields( + json, field_type_map: Mapping[str, FieldType], nullable: Optional[List] = [] +): for field in field_type_map: if field not in json: - return False + return field in nullable if field_type_map[field] is FieldType.INT: if not isinstance(json[field], int): return False @@ -22,19 +25,13 @@ def verify_json_fields(json, field_type_map: Mapping[str, FieldType]): elif field_type_map[field] is FieldType.EATERYID: if not isinstance(json[field], int) or EateryID(json[field]) == None: return False - + return True + def success_json(data): - return { - "success": True, - "data": data, - "error": None - } + return {"success": True, "data": data, "error": None} + def error_json(error: str): - return { - "success": False, - "data": None, - "error": error - } + return {"success": False, "data": None, "error": error} diff --git a/src/api/views.py b/src/api/views.py index 95e23bb..ccb4dc2 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -35,9 +35,11 @@ def post(self, request): if not verify_json_fields( json_body, { + "eatery_id": FieldType.INT or None, "type": FieldType.STRING, "content": FieldType.STRING, }, + ["eatery_id"], ): return JsonResponse(error_json("Malformed Request")) From 70a156c08a4340c6d4e9948f73cbc8859c57a87a Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Fri, 22 Apr 2022 19:41:34 -0400 Subject: [PATCH 096/305] Add deploy-dev.yml --- .github/workflows/deploy-dev.yml | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/deploy-dev.yml diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..b401b7a --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,44 @@ +name: Docker Build & Push and Deploy to eatery-blue-backend + +on: + push: + branches: [main] + +jobs: + path-context: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Get SHA + id: vars + run: echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" + - name: Docker Build & Push + uses: docker/build-push-action@v2 + with: + context: ./ + file: ./Dockerfile + push: true + tags: cornellappdev/eatery-blue-dev:${{ steps.vars.outputs.sha_short }} + - name: Remote SSH and Deploy + uses: appleboy/ssh-action@master + env: + IMAGE_TAG: ${{ steps.vars.outputs.sha_short }} + with: + host: ${{ secrets.DEV_SERVER_HOST }} + username: ${{ secrets.SERVER_USERNAME }} + key: ${{ secrets.DEV_SERVER_KEY }} + script: | + export IMAGE_TAG=${{ steps.vars.outputs.sha_short }} + cd docker-compose + docker stack rm the-stack + sleep 20s + docker stack deploy -c docker-compose.yml the-stack + docker system prune -a From 38fe98617776c0aeda10e5ff4bb5dd77cdfb6776 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Fri, 22 Apr 2022 19:44:07 -0400 Subject: [PATCH 097/305] Create README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbf54d2 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# eatery-blue-backend + +This is the backend for eatery-blue-backend. From 430f994e9867de8a33601910e19da4407ea660c9 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Fri, 22 Apr 2022 19:53:39 -0400 Subject: [PATCH 098/305] Fix deploy-dev.yml --- .github/workflows/deploy-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index b401b7a..67c5193 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -2,7 +2,7 @@ name: Docker Build & Push and Deploy to eatery-blue-backend on: push: - branches: [main] + branches: [master] jobs: path-context: From 23b430e22dbacd14eefb7e000d3d3fc1d94c78a8 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Fri, 22 Apr 2022 20:25:23 -0400 Subject: [PATCH 099/305] Add docker-compose from server --- docker-compose.server.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docker-compose.server.yml diff --git a/docker-compose.server.yml b/docker-compose.server.yml new file mode 100644 index 0000000..f7aa260 --- /dev/null +++ b/docker-compose.server.yml @@ -0,0 +1,10 @@ +version: '3' + +services: + app: + image: cornellappdev/eatery-blue-backend:${IMAGE_TAG} + env_file: .env + command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000" + ports: + - "8000:8000" + restart: always \ No newline at end of file From 270d0294a8c52217a35160a964e8df0efbdc77e5 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Wed, 27 Apr 2022 19:48:25 -0400 Subject: [PATCH 100/305] Change environment variables and install missing packages --- .envrctemplate | 9 +++++++++ requirements.txt | 1 + src/eatery_blue_backend/settings.py | 18 +++++++++--------- 3 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 .envrctemplate diff --git a/.envrctemplate b/.envrctemplate new file mode 100644 index 0000000..c1017d1 --- /dev/null +++ b/.envrctemplate @@ -0,0 +1,9 @@ +export CORNELL_VENDOR_TOKEN= +export CORNELL_VENDOR_API_KEY= +export DJANGO_ALLOWED_HOSTS= +export IS_PROD= +export POSTGRES_NAME= +export POSTGRES_USER= +export POSTGRES_PASSWORD= +export POSTGRES_HOST= +export POSTGRES_PORT= diff --git a/requirements.txt b/requirements.txt index a0381be..d86038e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ oauthlib==3.1.1 pathspec==0.9.0 platformdirs==2.4.0 protobuf==3.19.1 +psycopg2-binary==2.9.3 pyasn1==0.4.8 pyasn1-modules==0.2.8 pyparsing==3.0.6 diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index b6edd5b..33c1275 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -76,16 +76,9 @@ # Database # https://docs.djangoproject.com/en/4.0/ref/settings/#databases -IS_LOCAL = os.getenv("LOCAL") +IS_PROD = os.getenv("IS_PROD") == "True" -if IS_LOCAL == "True": - DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", - } - } -elif IS_LOCAL == "False": +if IS_PROD: DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql_psycopg2", @@ -96,6 +89,13 @@ "PORT": os.getenv("POSTGRES_PORT"), } } +else: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } + } # Password validation From b2373371cd097a16475167333163c6edb19b3696 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Wed, 27 Apr 2022 17:49:06 -0400 Subject: [PATCH 101/305] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index bbf54d2..3ed2ff6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ # eatery-blue-backend This is the backend for eatery-blue-backend. + +SP22 Members +-------------- +- Marya Kim +- Archit Mehta From 886b98716c8657e1815e77a9d64170d26e324202 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Wed, 27 Apr 2022 18:31:04 -0400 Subject: [PATCH 102/305] Update Dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0086eab..7535b63 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,4 +4,5 @@ RUN mkdir /usr/app WORKDIR /usr/app COPY ./src . COPY ./requirements.txt . -RUN pip install -r requirements.txt \ No newline at end of file +RUN pip install -r requirements.txt +CMD python3 manage.py runserver From b210c575fe723d710af7c9a507e9b8b515d29b47 Mon Sep 17 00:00:00 2001 From: Gonzalo Gonzalez Date: Wed, 27 Apr 2022 20:54:09 -0400 Subject: [PATCH 103/305] Remove expose --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 514ea27..0086eab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,5 +4,4 @@ RUN mkdir /usr/app WORKDIR /usr/app COPY ./src . COPY ./requirements.txt . -RUN pip install -r requirements.txt -EXPOSE 8000 +RUN pip install -r requirements.txt \ No newline at end of file From 5d66ccfa89543de3de6c0ed9bf979aa4348cbf2f Mon Sep 17 00:00:00 2001 From: Archit404Error <4architmehta@gmail.com> Date: Sun, 8 May 2022 19:34:27 -0400 Subject: [PATCH 104/305] Update UpdateView to EateryView --- src/api/urls.py | 4 ++-- src/eatery/views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/urls.py b/src/api/urls.py index 5472297..d1d6989 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -1,10 +1,10 @@ from django.urls import path -from eatery.views import UpdateView +from eatery.views import EateryView from api.views import MainDfgView, ReportView urlpatterns = [ path("", MainDfgView.as_view(), name="main"), - path("update", UpdateView.as_view(), name="update"), + path("update", EateryView.as_view(), name="update"), path("report", ReportView.as_view(), name="report"), ] diff --git a/src/eatery/views.py b/src/eatery/views.py index 8bd3abe..807e201 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -8,7 +8,7 @@ from .controllers.update_eatery import UpdateEateryController -class UpdateView(APIView): +class EateryView(APIView): def post(self, request): text_params = request.POST if not verify_json_fields( From e90628a7ac68cda9ba26f475bbea88261305c489 Mon Sep 17 00:00:00 2001 From: Marya Kim Date: Wed, 14 Sep 2022 16:47:46 -0400 Subject: [PATCH 105/305] Remove migrations and reload data --- ...ayofweekeventschedule_end_time_and_more.py | 50 ----------------- ...03_alter_dayofweekeventschedule_offsets.py | 20 ------- ...fsets_dayofweekeventschedule_offset_lst.py | 18 ------- ...emove_dayofweekeventschedule_offset_lst.py | 17 ------ .../0006_dayofweekeventschedule_offset_lst.py | 21 -------- ...eekeventschedule_repeatingeventschedule.py | 17 ------ ...emove_dateeventschedule_eatery_and_more.py | 38 ------------- .../0009_scheduleexception_exception_type.py | 19 ------- ...ter_scheduleexception_end_time_and_more.py | 23 -------- .../0011_scheduleexception_eatery.py | 20 ------- .../0012_alter_reportstore_eatery.py | 19 ------- ..._eatery_alter_itemstore_eatery_and_more.py | 53 ------------------- 12 files changed, 315 deletions(-) delete mode 100644 src/api/migrations/0002_rename_end_dayofweekeventschedule_end_time_and_more.py delete mode 100644 src/api/migrations/0003_alter_dayofweekeventschedule_offsets.py delete mode 100644 src/api/migrations/0004_rename_offsets_dayofweekeventschedule_offset_lst.py delete mode 100644 src/api/migrations/0005_remove_dayofweekeventschedule_offset_lst.py delete mode 100644 src/api/migrations/0006_dayofweekeventschedule_offset_lst.py delete mode 100644 src/api/migrations/0007_rename_dayofweekeventschedule_repeatingeventschedule.py delete mode 100644 src/api/migrations/0008_scheduleexception_remove_dateeventschedule_eatery_and_more.py delete mode 100644 src/api/migrations/0009_scheduleexception_exception_type.py delete mode 100644 src/api/migrations/0010_alter_scheduleexception_end_time_and_more.py delete mode 100644 src/api/migrations/0011_scheduleexception_eatery.py delete mode 100644 src/api/migrations/0012_alter_reportstore_eatery.py delete mode 100644 src/api/migrations/0013_alter_alertstore_eatery_alter_itemstore_eatery_and_more.py diff --git a/src/api/migrations/0002_rename_end_dayofweekeventschedule_end_time_and_more.py b/src/api/migrations/0002_rename_end_dayofweekeventschedule_end_time_and_more.py deleted file mode 100644 index 954542c..0000000 --- a/src/api/migrations/0002_rename_end_dayofweekeventschedule_end_time_and_more.py +++ /dev/null @@ -1,50 +0,0 @@ -# Generated by Django 4.0 on 2022-03-24 20:16 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0001_initial'), - ] - - operations = [ - migrations.RenameField( - model_name='dayofweekeventschedule', - old_name='end', - new_name='end_time', - ), - migrations.RenameField( - model_name='dayofweekeventschedule', - old_name='start', - new_name='start_time', - ), - migrations.AlterUniqueTogether( - name='dayofweekeventschedule', - unique_together={('eatery', 'event_description')}, - ), - migrations.AddField( - model_name='dayofweekeventschedule', - name='offsets', - field=models.JSONField(default=''), - preserve_default=False, - ), - migrations.AddField( - model_name='dayofweekeventschedule', - name='repeat_interval', - field=models.IntegerField(default=7), - preserve_default=False, - ), - migrations.AddField( - model_name='dayofweekeventschedule', - name='start_date', - field=models.DateField(default=django.utils.timezone.now), - preserve_default=False, - ), - migrations.RemoveField( - model_name='dayofweekeventschedule', - name='day_of_week', - ), - ] diff --git a/src/api/migrations/0003_alter_dayofweekeventschedule_offsets.py b/src/api/migrations/0003_alter_dayofweekeventschedule_offsets.py deleted file mode 100644 index f82fcd8..0000000 --- a/src/api/migrations/0003_alter_dayofweekeventschedule_offsets.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.0 on 2022-03-24 20:22 - -import django.core.validators -from django.db import migrations, models -import re - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0002_rename_end_dayofweekeventschedule_end_time_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='dayofweekeventschedule', - name='offsets', - field=models.CharField(max_length=100, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')]), - ), - ] diff --git a/src/api/migrations/0004_rename_offsets_dayofweekeventschedule_offset_lst.py b/src/api/migrations/0004_rename_offsets_dayofweekeventschedule_offset_lst.py deleted file mode 100644 index ba9cb5d..0000000 --- a/src/api/migrations/0004_rename_offsets_dayofweekeventschedule_offset_lst.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0 on 2022-03-24 20:25 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0003_alter_dayofweekeventschedule_offsets'), - ] - - operations = [ - migrations.RenameField( - model_name='dayofweekeventschedule', - old_name='offsets', - new_name='offset_lst', - ), - ] diff --git a/src/api/migrations/0005_remove_dayofweekeventschedule_offset_lst.py b/src/api/migrations/0005_remove_dayofweekeventschedule_offset_lst.py deleted file mode 100644 index b4ae667..0000000 --- a/src/api/migrations/0005_remove_dayofweekeventschedule_offset_lst.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.0 on 2022-03-24 20:26 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0004_rename_offsets_dayofweekeventschedule_offset_lst'), - ] - - operations = [ - migrations.RemoveField( - model_name='dayofweekeventschedule', - name='offset_lst', - ), - ] diff --git a/src/api/migrations/0006_dayofweekeventschedule_offset_lst.py b/src/api/migrations/0006_dayofweekeventschedule_offset_lst.py deleted file mode 100644 index 83311f4..0000000 --- a/src/api/migrations/0006_dayofweekeventschedule_offset_lst.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.0 on 2022-03-24 20:28 - -import django.core.validators -from django.db import migrations, models -import re - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0005_remove_dayofweekeventschedule_offset_lst'), - ] - - operations = [ - migrations.AddField( - model_name='dayofweekeventschedule', - name='offset_lst', - field=models.CharField(default='0,2', max_length=100, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')]), - preserve_default=False, - ), - ] diff --git a/src/api/migrations/0007_rename_dayofweekeventschedule_repeatingeventschedule.py b/src/api/migrations/0007_rename_dayofweekeventschedule_repeatingeventschedule.py deleted file mode 100644 index f11c20c..0000000 --- a/src/api/migrations/0007_rename_dayofweekeventschedule_repeatingeventschedule.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.0 on 2022-03-25 18:19 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0006_dayofweekeventschedule_offset_lst'), - ] - - operations = [ - migrations.RenameModel( - old_name='DayOfWeekEventSchedule', - new_name='RepeatingEventSchedule', - ), - ] diff --git a/src/api/migrations/0008_scheduleexception_remove_dateeventschedule_eatery_and_more.py b/src/api/migrations/0008_scheduleexception_remove_dateeventschedule_eatery_and_more.py deleted file mode 100644 index 0f2f17a..0000000 --- a/src/api/migrations/0008_scheduleexception_remove_dateeventschedule_eatery_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 4.0 on 2022-04-13 02:48 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0007_rename_dayofweekeventschedule_repeatingeventschedule'), - ] - - operations = [ - migrations.CreateModel( - name='ScheduleException', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('date', models.DateField()), - ('start_time', models.DateField(blank=True, null=True)), - ('end_time', models.DateField(blank=True, null=True)), - ('parent', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.repeatingeventschedule')), - ], - ), - migrations.RemoveField( - model_name='dateeventschedule', - name='eatery', - ), - migrations.RemoveField( - model_name='dateeventschedule', - name='menu', - ), - migrations.DeleteModel( - name='ClosedEventSchedule', - ), - migrations.DeleteModel( - name='DateEventSchedule', - ), - ] diff --git a/src/api/migrations/0009_scheduleexception_exception_type.py b/src/api/migrations/0009_scheduleexception_exception_type.py deleted file mode 100644 index 29349f3..0000000 --- a/src/api/migrations/0009_scheduleexception_exception_type.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.0 on 2022-04-13 20:45 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0008_scheduleexception_remove_dateeventschedule_eatery_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='scheduleexception', - name='exception_type', - field=models.CharField(choices=[('closed', 'Closed'), ('modified', 'Modified')], default='modified', max_length=10), - preserve_default=False, - ), - ] diff --git a/src/api/migrations/0010_alter_scheduleexception_end_time_and_more.py b/src/api/migrations/0010_alter_scheduleexception_end_time_and_more.py deleted file mode 100644 index 9bf7634..0000000 --- a/src/api/migrations/0010_alter_scheduleexception_end_time_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.0 on 2022-04-17 03:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0009_scheduleexception_exception_type'), - ] - - operations = [ - migrations.AlterField( - model_name='scheduleexception', - name='end_time', - field=models.TimeField(blank=True, null=True), - ), - migrations.AlterField( - model_name='scheduleexception', - name='start_time', - field=models.TimeField(blank=True, null=True), - ), - ] diff --git a/src/api/migrations/0011_scheduleexception_eatery.py b/src/api/migrations/0011_scheduleexception_eatery.py deleted file mode 100644 index d9dd68b..0000000 --- a/src/api/migrations/0011_scheduleexception_eatery.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.0 on 2022-04-17 03:49 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0010_alter_scheduleexception_end_time_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='scheduleexception', - name='eatery', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore'), - preserve_default=False, - ), - ] diff --git a/src/api/migrations/0012_alter_reportstore_eatery.py b/src/api/migrations/0012_alter_reportstore_eatery.py deleted file mode 100644 index f6a6ccf..0000000 --- a/src/api/migrations/0012_alter_reportstore_eatery.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.0 on 2022-04-19 00:44 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0011_scheduleexception_eatery'), - ] - - operations = [ - migrations.AlterField( - model_name='reportstore', - name='eatery', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore'), - ), - ] diff --git a/src/api/migrations/0013_alter_alertstore_eatery_alter_itemstore_eatery_and_more.py b/src/api/migrations/0013_alter_alertstore_eatery_alter_itemstore_eatery_and_more.py deleted file mode 100644 index f65d906..0000000 --- a/src/api/migrations/0013_alter_alertstore_eatery_alter_itemstore_eatery_and_more.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 4.0 on 2022-04-23 22:19 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('eatery', '0001_initial'), - ('api', '0012_alter_reportstore_eatery'), - ] - - operations = [ - migrations.AlterField( - model_name='alertstore', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='itemstore', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='menustore', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='repeatingeventschedule', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='reportstore', - name='eatery', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='scheduleexception', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='transactionhistorystore', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.DeleteModel( - name='EateryStore', - ), - ] From 6f5170c26bcd467e2222abdcca36e4054550b9f8 Mon Sep 17 00:00:00 2001 From: Marya Kim <78518634+markim21@users.noreply.github.com> Date: Thu, 22 Dec 2022 15:03:01 -0500 Subject: [PATCH 106/305] Replace DFG mess with MVC (#29) * decoupled reports and alerts * Separate apps underneath api folder. Repopulate db with CornellDiningNow * Create EventModel and set up population from CornellDiningNow * sort necessary folders in src and add to installed apps * Update Alert model name and serializers * Implement eatery event controllers * Change EateryStore to Eatery * Change EateryStore to Eatery * Populate eatery events from CDN. Problem with relating Event models? * Fix type error with creating events * Using validators and serializers to interact w models * Small errors * WindowsPath bug fix * Create apps for menu, category, item * Create parsing controllers for menu, item, category apps * Parse and populate models for event, menu, category, item * CDN parsing bug fixes * Fix item serializer bug that included foreignkey * Implement nested serializers between eatery and event. I messed up the menu relations - I need to fix that * Fix serialization relationships between category, menu, item * Fix serializer and model not_null constraints, add and remove default values as necessary. temporarily ignore items * Fix category creating objects. Add event to admin. * Implement creating items * Clean up whitespace and debugging * Add Novicks Cafe. Explicitly define eatery ids * Implement ModelViewSets for eatery * Ingest images and new eateries. not working * Add documentation, comments to clarify code --- README.md | 5 + src/alert/admin.py | 4 + src/alert/apps.py | 4 + src/{ => alert/controllers}/api/__init__.py | 0 .../api/controllers/create_transaction.py | 0 .../controllers/delete_all_transactions.py | 0 src/{ => alert/controllers}/api/dfg/main.py | 1 + .../api/dfg/nodes/CornellDiningNow.py | 0 .../controllers}/api/dfg/nodes/DfgNode.py | 0 .../api/dfg/nodes/EateriesFromDB.py | 38 +----- .../controllers}/api/dfg/nodes/EateryStubs.py | 0 .../controllers}/api/dfg/nodes/__init__.py | 0 .../api/dfg/nodes/macros/EateryEvents.py | 2 +- .../api/dfg/nodes/macros/LeftMergeEateries.py | 0 .../api/dfg/nodes/macros/LeftMergeEvents.py | 3 +- .../nodes/macros/LeftMergeRegularEvents.py | 0 .../nodes/macros/LeftMergeRepeatedEvents.py | 0 .../api/dfg/nodes/system/ConvertFromJson.py | 23 +--- .../api/dfg/nodes/system/ConvertToJson.py | 2 +- .../dfg/nodes/system/DictResponseWrapper.py | 0 .../api/dfg/nodes/system/EateryGenerator.py | 0 .../api/dfg/nodes/system/InMemoryCache.py | 0 .../api/dfg/nodes/system/LeftMerge.py | 0 .../api/dfg/nodes/system/Mapping.py | 0 .../dfg/nodes/wait_times/WaitTimeFilter.py | 0 .../api/dfg/nodes/wait_times/WaitTimes.py | 2 +- .../management/commands/export_db_snapshot.py | 0 .../management/commands/ingest_db_snapshot.py | 0 .../commands/ingest_log_transactions.py | 0 .../commands/ingest_recent_transactions.py | 0 .../api/migrations/0001_initial.py | 0 ...ventschedule_scheduleexception_and_more.py | 111 ++++++++++++++++ .../controllers}/api/migrations/__init__.py | 0 .../api/models/TransactionModel.py | 0 src/alert/controllers/api/models/__init__.py | 9 ++ .../controllers}/api/serializers.py | 10 +- src/alert/controllers/misc_code.py | 27 ++++ src/alert/migrations/0001_initial.py | 26 ++++ src/alert/migrations/__init__.py | 0 .../models/AlertModel.py => alert/models.py} | 6 +- src/alert/serializers.py | 31 +++++ src/alert/urls.py | 10 ++ src/alert/views.py | 30 +++++ src/api/admin.py | 16 --- src/api/models/EventScheduleModel.py | 53 -------- src/api/models/MenuModel.py | 43 ------ src/api/models/__init__.py | 16 --- src/api/urls.py | 10 -- src/category/__init__.py | 0 src/category/admin.py | 4 + src/category/apps.py | 6 + src/category/controllers/populate_category.py | 106 +++++++++++++++ src/category/migrations/0001_initial.py | 24 ++++ .../migrations/0002_alter_category_menu.py | 20 +++ .../migrations/0003_alter_category_menu.py | 21 +++ src/category/migrations/__init__.py | 0 src/category/models.py | 8 ++ src/category/serializers.py | 17 +++ src/category/tests.py | 3 + src/category/views.py | 3 + src/cdn_parser/__init__.py | 0 src/cdn_parser/admin.py | 3 + src/cdn_parser/apps.py | 6 + src/cdn_parser/controllers/filter_models.py | 0 src/cdn_parser/controllers/populate_models.py | 67 ++++++++++ src/cdn_parser/migrations/__init__.py | 0 src/cdn_parser/tests.py | 3 + src/cdn_parser/urls.py | 6 + src/cdn_parser/views.py | 12 ++ src/eatery/admin.py | 5 +- src/eatery/controllers/populate_eatery.py | 86 ++++++++++++ src/eatery/controllers/update_eatery.py | 14 +- src/eatery/datatype/Eatery.py | 116 +---------------- src/eatery/migrations/0001_initial.py | 8 +- .../0002_alter_eatery_menu_summary.py | 18 +++ src/eatery/models.py | 8 +- src/eatery/serializers.py | 27 +++- src/eatery/urls.py | 19 +++ src/{api => eatery}/util/constants.py | 4 +- src/eatery/util/convert_from_json.py | 25 ++++ src/eatery/util/eatery_store.txt | 38 ++++++ src/{api => eatery}/util/json.py | 0 src/{api => eatery}/util/time.py | 0 src/eatery/views.py | 53 +++++++- src/eatery_blue_backend/settings.py | 10 +- src/eatery_blue_backend/urls.py | 6 +- src/event/__init__.py | 0 src/event/admin.py | 4 + src/event/apps.py | 5 + .../controllers}/ClosedSchedule.py | 2 +- src/event/controllers/populate_event.py | 70 ++++++++++ .../update_models/CornellDiningEvents.py | 123 ++++++++++++++++++ .../update_models/CornellDiningNow.py | 52 ++++++++ .../schedule/CacheMenuInjection.py | 0 .../schedule/CornellDiningEvents.py | 11 +- .../schedule/ModifiedSchedules.py | 4 +- .../schedule/RepeatingSchedule.py | 2 +- src/{api => event}/datatype/Event.py | 7 +- src/{api => event}/datatype/Menu.py | 2 +- src/{api => event}/datatype/MenuCategory.py | 0 src/{api => event}/datatype/MenuItem.py | 7 - .../datatype/MenuItemSection.py | 0 src/{api => event}/datatype/MenuSubItem.py | 0 src/{api => event}/datatype/WaitTime.py | 0 src/{api => event}/datatype/WaitTimesDay.py | 0 src/event/migrations/0001_initial.py | 26 ++++ .../0002_alter_event_end_alter_event_start.py | 23 ++++ src/event/migrations/__init__.py | 0 src/event/models.py | 20 +++ src/event/serializers.py | 19 +++ src/event/urls.py | 7 + src/event/views.py | 13 ++ src/item/__init__.py | 0 src/item/admin.py | 4 + src/{api => item}/apps.py | 4 +- src/item/controllers/populate_item.py | 72 ++++++++++ src/item/migrations/0001_initial.py | 25 ++++ .../migrations/0002_alter_item_base_price.py | 18 +++ src/item/migrations/__init__.py | 0 src/item/models.py | 9 ++ src/item/serializers.py | 14 ++ src/item/tests.py | 3 + src/item/views.py | 3 + src/menu/__init__.py | 0 src/menu/admin.py | 4 + src/menu/apps.py | 6 + src/menu/controllers/populate_menu.py | 42 ++++++ src/menu/migrations/0001_initial.py | 23 ++++ src/menu/migrations/__init__.py | 0 src/menu/models.py | 6 + src/menu/serializers.py | 15 +++ src/menu/tests.py | 3 + src/menu/views.py | 3 + src/reports/__init__.py | 0 src/reports/admin.py | 4 + src/reports/apps.py | 4 + .../controllers/create_report.py | 0 .../ReportModel.py => reports/models.py} | 4 +- src/reports/urls.py | 10 ++ src/{api => reports}/views.py | 24 +--- 140 files changed, 1531 insertions(+), 394 deletions(-) create mode 100644 src/alert/admin.py create mode 100644 src/alert/apps.py rename src/{ => alert/controllers}/api/__init__.py (100%) rename src/{ => alert/controllers}/api/controllers/create_transaction.py (100%) rename src/{ => alert/controllers}/api/controllers/delete_all_transactions.py (100%) rename src/{ => alert/controllers}/api/dfg/main.py (98%) rename src/{ => alert/controllers}/api/dfg/nodes/CornellDiningNow.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/DfgNode.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/EateriesFromDB.py (58%) rename src/{ => alert/controllers}/api/dfg/nodes/EateryStubs.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/__init__.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/macros/EateryEvents.py (95%) rename src/{ => alert/controllers}/api/dfg/nodes/macros/LeftMergeEateries.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/macros/LeftMergeEvents.py (94%) rename src/{ => alert/controllers}/api/dfg/nodes/macros/LeftMergeRegularEvents.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/system/ConvertFromJson.py (52%) rename src/{ => alert/controllers}/api/dfg/nodes/system/ConvertToJson.py (95%) rename src/{ => alert/controllers}/api/dfg/nodes/system/DictResponseWrapper.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/system/EateryGenerator.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/system/InMemoryCache.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/system/LeftMerge.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/system/Mapping.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/wait_times/WaitTimeFilter.py (100%) rename src/{ => alert/controllers}/api/dfg/nodes/wait_times/WaitTimes.py (99%) rename src/{ => alert/controllers}/api/management/commands/export_db_snapshot.py (100%) rename src/{ => alert/controllers}/api/management/commands/ingest_db_snapshot.py (100%) rename src/{ => alert/controllers}/api/management/commands/ingest_log_transactions.py (100%) rename src/{ => alert/controllers}/api/management/commands/ingest_recent_transactions.py (100%) rename src/{ => alert/controllers}/api/migrations/0001_initial.py (100%) create mode 100644 src/alert/controllers/api/migrations/0002_repeatingeventschedule_scheduleexception_and_more.py rename src/{ => alert/controllers}/api/migrations/__init__.py (100%) rename src/{ => alert/controllers}/api/models/TransactionModel.py (100%) create mode 100644 src/alert/controllers/api/models/__init__.py rename src/{ => alert/controllers}/api/serializers.py (88%) create mode 100644 src/alert/controllers/misc_code.py create mode 100644 src/alert/migrations/0001_initial.py create mode 100644 src/alert/migrations/__init__.py rename src/{api/models/AlertModel.py => alert/models.py} (60%) create mode 100644 src/alert/serializers.py create mode 100644 src/alert/urls.py create mode 100644 src/alert/views.py delete mode 100644 src/api/admin.py delete mode 100644 src/api/models/EventScheduleModel.py delete mode 100644 src/api/models/MenuModel.py delete mode 100644 src/api/models/__init__.py delete mode 100644 src/api/urls.py create mode 100644 src/category/__init__.py create mode 100644 src/category/admin.py create mode 100644 src/category/apps.py create mode 100644 src/category/controllers/populate_category.py create mode 100644 src/category/migrations/0001_initial.py create mode 100644 src/category/migrations/0002_alter_category_menu.py create mode 100644 src/category/migrations/0003_alter_category_menu.py create mode 100644 src/category/migrations/__init__.py create mode 100644 src/category/models.py create mode 100644 src/category/serializers.py create mode 100644 src/category/tests.py create mode 100644 src/category/views.py create mode 100644 src/cdn_parser/__init__.py create mode 100644 src/cdn_parser/admin.py create mode 100644 src/cdn_parser/apps.py create mode 100644 src/cdn_parser/controllers/filter_models.py create mode 100644 src/cdn_parser/controllers/populate_models.py create mode 100644 src/cdn_parser/migrations/__init__.py create mode 100644 src/cdn_parser/tests.py create mode 100644 src/cdn_parser/urls.py create mode 100644 src/cdn_parser/views.py create mode 100644 src/eatery/controllers/populate_eatery.py create mode 100644 src/eatery/migrations/0002_alter_eatery_menu_summary.py create mode 100644 src/eatery/urls.py rename src/{api => eatery}/util/constants.py (99%) create mode 100644 src/eatery/util/convert_from_json.py create mode 100644 src/eatery/util/eatery_store.txt rename src/{api => eatery}/util/json.py (100%) rename src/{api => eatery}/util/time.py (100%) create mode 100644 src/event/__init__.py create mode 100644 src/event/admin.py create mode 100644 src/event/apps.py rename src/{api/dfg/nodes/schedule => event/controllers}/ClosedSchedule.py (95%) create mode 100644 src/event/controllers/populate_event.py create mode 100644 src/event/controllers/update_models/CornellDiningEvents.py create mode 100644 src/event/controllers/update_models/CornellDiningNow.py rename src/{api/dfg/nodes => event/controllers/update_models}/schedule/CacheMenuInjection.py (100%) rename src/{api/dfg/nodes => event/controllers/update_models}/schedule/CornellDiningEvents.py (93%) rename src/{api/dfg/nodes => event/controllers/update_models}/schedule/ModifiedSchedules.py (94%) rename src/{api/dfg/nodes => event/controllers/update_models}/schedule/RepeatingSchedule.py (98%) rename src/{api => event}/datatype/Event.py (97%) rename src/{api => event}/datatype/Menu.py (87%) rename src/{api => event}/datatype/MenuCategory.py (100%) rename src/{api => event}/datatype/MenuItem.py (87%) rename src/{api => event}/datatype/MenuItemSection.py (100%) rename src/{api => event}/datatype/MenuSubItem.py (100%) rename src/{api => event}/datatype/WaitTime.py (100%) rename src/{api => event}/datatype/WaitTimesDay.py (100%) create mode 100644 src/event/migrations/0001_initial.py create mode 100644 src/event/migrations/0002_alter_event_end_alter_event_start.py create mode 100644 src/event/migrations/__init__.py create mode 100644 src/event/models.py create mode 100644 src/event/serializers.py create mode 100644 src/event/urls.py create mode 100644 src/event/views.py create mode 100644 src/item/__init__.py create mode 100644 src/item/admin.py rename src/{api => item}/apps.py (66%) create mode 100644 src/item/controllers/populate_item.py create mode 100644 src/item/migrations/0001_initial.py create mode 100644 src/item/migrations/0002_alter_item_base_price.py create mode 100644 src/item/migrations/__init__.py create mode 100644 src/item/models.py create mode 100644 src/item/serializers.py create mode 100644 src/item/tests.py create mode 100644 src/item/views.py create mode 100644 src/menu/__init__.py create mode 100644 src/menu/admin.py create mode 100644 src/menu/apps.py create mode 100644 src/menu/controllers/populate_menu.py create mode 100644 src/menu/migrations/0001_initial.py create mode 100644 src/menu/migrations/__init__.py create mode 100644 src/menu/models.py create mode 100644 src/menu/serializers.py create mode 100644 src/menu/tests.py create mode 100644 src/menu/views.py create mode 100644 src/reports/__init__.py create mode 100644 src/reports/admin.py create mode 100644 src/reports/apps.py rename src/{api => reports}/controllers/create_report.py (100%) rename src/{api/models/ReportModel.py => reports/models.py} (60%) create mode 100644 src/reports/urls.py rename src/{api => reports}/views.py (61%) diff --git a/README.md b/README.md index 3ed2ff6..a2e0df4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ This is the backend for eatery-blue-backend. +FA22 Members +--------------- +- Marya Kim +- Sasha Loayza + SP22 Members -------------- - Marya Kim diff --git a/src/alert/admin.py b/src/alert/admin.py new file mode 100644 index 0000000..707c401 --- /dev/null +++ b/src/alert/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from alert.models import Alert + +admin.site.register(Alert) diff --git a/src/alert/apps.py b/src/alert/apps.py new file mode 100644 index 0000000..ddb6053 --- /dev/null +++ b/src/alert/apps.py @@ -0,0 +1,4 @@ +from django.apps import AppConfig + +class AlertConfig(AppConfig): + name = "alert" \ No newline at end of file diff --git a/src/api/__init__.py b/src/alert/controllers/api/__init__.py similarity index 100% rename from src/api/__init__.py rename to src/alert/controllers/api/__init__.py diff --git a/src/api/controllers/create_transaction.py b/src/alert/controllers/api/controllers/create_transaction.py similarity index 100% rename from src/api/controllers/create_transaction.py rename to src/alert/controllers/api/controllers/create_transaction.py diff --git a/src/api/controllers/delete_all_transactions.py b/src/alert/controllers/api/controllers/delete_all_transactions.py similarity index 100% rename from src/api/controllers/delete_all_transactions.py rename to src/alert/controllers/api/controllers/delete_all_transactions.py diff --git a/src/api/dfg/main.py b/src/alert/controllers/api/dfg/main.py similarity index 98% rename from src/api/dfg/main.py rename to src/alert/controllers/api/dfg/main.py index 2a12997..c5be9f9 100644 --- a/src/api/dfg/main.py +++ b/src/alert/controllers/api/dfg/main.py @@ -1,3 +1,4 @@ +from typing import Dict from api.dfg.nodes.CornellDiningNow import CornellDiningNow from api.dfg.nodes.EateriesFromDB import EateriesFromDB from api.dfg.nodes.EateryStubs import EateryStubs diff --git a/src/api/dfg/nodes/CornellDiningNow.py b/src/alert/controllers/api/dfg/nodes/CornellDiningNow.py similarity index 100% rename from src/api/dfg/nodes/CornellDiningNow.py rename to src/alert/controllers/api/dfg/nodes/CornellDiningNow.py diff --git a/src/api/dfg/nodes/DfgNode.py b/src/alert/controllers/api/dfg/nodes/DfgNode.py similarity index 100% rename from src/api/dfg/nodes/DfgNode.py rename to src/alert/controllers/api/dfg/nodes/DfgNode.py diff --git a/src/api/dfg/nodes/EateriesFromDB.py b/src/alert/controllers/api/dfg/nodes/EateriesFromDB.py similarity index 58% rename from src/api/dfg/nodes/EateriesFromDB.py rename to src/alert/controllers/api/dfg/nodes/EateriesFromDB.py index f9b28c7..96069fb 100644 --- a/src/api/dfg/nodes/EateriesFromDB.py +++ b/src/alert/controllers/api/dfg/nodes/EateriesFromDB.py @@ -1,10 +1,7 @@ from datetime import datetime from api.dfg.nodes.DfgNode import DfgNode -from api.models import AlertStore -from api.serializers import AlertStoreSerializer from eatery.datatype.Eatery import Eatery, EateryID -from eatery.datatype.EateryAlert import EateryAlert from eatery.models import EateryStore from eatery.serializers import EateryStoreSerializer @@ -16,20 +13,8 @@ def __call__(self, *args, **kwargs) -> list[Eatery]: eateries = EateryStore.objects.all() serialized_eateries = EateryStoreSerializer(data=eateries, many=True) serialized_eateries.is_valid() - alerts = AlertStore.objects.filter( - end_timestamp__gte=datetime.now().timestamp(), - start_timestamp__lte=datetime.now().timestamp(), - ) - serialized_alerts = AlertStoreSerializer(data=alerts, many=True) - serialized_alerts.is_valid() - return list( - map( - lambda x: EateriesFromDB.eatery_from_serialized( - x, serialized_alerts.data - ), - serialized_eateries.data, - ) - ) + return list(serialized_eateries.data) + @staticmethod def none_repr(str): @@ -57,24 +42,7 @@ def eatery_from_serialized( online_order_url=EateriesFromDB.none_repr( serialized_eatery["online_order_url"] ), - alerts=EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts), - ) - - @staticmethod - def alerts(eatery_id: int, serialized_alerts: list[dict]): - return [ - EateriesFromDB.alert_from_serialized(alert) - for alert in serialized_alerts - if alert["eatery"] == eatery_id - ] - - @staticmethod - def alert_from_serialized(serialized_alert: dict): - return EateryAlert( - id=serialized_alert["id"], - description=serialized_alert["description"], - start_timestamp=serialized_alert["start_timestamp"], - end_timestamp=serialized_alert["end_timestamp"], + ) def description(self): diff --git a/src/api/dfg/nodes/EateryStubs.py b/src/alert/controllers/api/dfg/nodes/EateryStubs.py similarity index 100% rename from src/api/dfg/nodes/EateryStubs.py rename to src/alert/controllers/api/dfg/nodes/EateryStubs.py diff --git a/src/api/dfg/nodes/__init__.py b/src/alert/controllers/api/dfg/nodes/__init__.py similarity index 100% rename from src/api/dfg/nodes/__init__.py rename to src/alert/controllers/api/dfg/nodes/__init__.py diff --git a/src/api/dfg/nodes/macros/EateryEvents.py b/src/alert/controllers/api/dfg/nodes/macros/EateryEvents.py similarity index 95% rename from src/api/dfg/nodes/macros/EateryEvents.py rename to src/alert/controllers/api/dfg/nodes/macros/EateryEvents.py index 0bab2d5..38d55a8 100644 --- a/src/api/dfg/nodes/macros/EateryEvents.py +++ b/src/alert/controllers/api/dfg/nodes/macros/EateryEvents.py @@ -2,7 +2,7 @@ from api.dfg.nodes.macros.LeftMergeRegularEvents import LeftMergeRegularEvents from api.dfg.nodes.macros.LeftMergeRepeatedEvents import LeftMergeRepeatedEvents from api.dfg.nodes.schedule.CacheMenuInjection import CacheMenuInjection -from api.dfg.nodes.schedule.ClosedSchedule import ClosedSchedule +from event.controllers.ClosedSchedule import ClosedSchedule from api.dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents from api.dfg.nodes.schedule.ModifiedSchedules import ModifiedSchedules from api.dfg.nodes.schedule.RepeatingSchedule import RepeatingSchedule diff --git a/src/api/dfg/nodes/macros/LeftMergeEateries.py b/src/alert/controllers/api/dfg/nodes/macros/LeftMergeEateries.py similarity index 100% rename from src/api/dfg/nodes/macros/LeftMergeEateries.py rename to src/alert/controllers/api/dfg/nodes/macros/LeftMergeEateries.py diff --git a/src/api/dfg/nodes/macros/LeftMergeEvents.py b/src/alert/controllers/api/dfg/nodes/macros/LeftMergeEvents.py similarity index 94% rename from src/api/dfg/nodes/macros/LeftMergeEvents.py rename to src/alert/controllers/api/dfg/nodes/macros/LeftMergeEvents.py index 2188d4b..19eb6cc 100644 --- a/src/api/dfg/nodes/macros/LeftMergeEvents.py +++ b/src/alert/controllers/api/dfg/nodes/macros/LeftMergeEvents.py @@ -1,4 +1,4 @@ -from api.dfg.nodes.DfgNode import DfgNode +"""from api.dfg.nodes.DfgNode import DfgNode from api.dfg.nodes.system.ConvertFromJson import EventFromJson from api.dfg.nodes.system.ConvertToJson import ConvertToJson from api.dfg.nodes.system.LeftMerge import LeftMerge @@ -26,3 +26,4 @@ def __call__(self, *args, **kwargs): def description(self): return "LeftMergeEvents" +""" \ No newline at end of file diff --git a/src/api/dfg/nodes/macros/LeftMergeRegularEvents.py b/src/alert/controllers/api/dfg/nodes/macros/LeftMergeRegularEvents.py similarity index 100% rename from src/api/dfg/nodes/macros/LeftMergeRegularEvents.py rename to src/alert/controllers/api/dfg/nodes/macros/LeftMergeRegularEvents.py diff --git a/src/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py b/src/alert/controllers/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py similarity index 100% rename from src/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py rename to src/alert/controllers/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py diff --git a/src/api/dfg/nodes/system/ConvertFromJson.py b/src/alert/controllers/api/dfg/nodes/system/ConvertFromJson.py similarity index 52% rename from src/api/dfg/nodes/system/ConvertFromJson.py rename to src/alert/controllers/api/dfg/nodes/system/ConvertFromJson.py index 5ac993f..b440739 100644 --- a/src/api/dfg/nodes/system/ConvertFromJson.py +++ b/src/alert/controllers/api/dfg/nodes/system/ConvertFromJson.py @@ -1,6 +1,6 @@ from typing import Union -from api.datatype.Event import Event + from api.dfg.nodes.DfgNode import DfgNode from eatery.datatype.Eatery import Eatery @@ -26,24 +26,3 @@ def from_json(obj: Union[list, dict], *args, **kwargs): def description(self): return "EateryFromJson" - -class EventFromJson(DfgNode): - def __init__(self, child: DfgNode): - self.child = child - - def __call__(self, *args, **kwargs): - result = self.child(*args, **kwargs) - return EventFromJson.from_json(result, *args, **kwargs) - - def children(self): - return [self.child] - - @staticmethod - def from_json(obj: Union[list, dict], *args, **kwargs): - if isinstance(obj, list): - return [EventFromJson.from_json(elem, *args, **kwargs) for elem in obj] - else: - return Event.from_json(obj) - - def description(self): - return "ConvertFromJson" diff --git a/src/api/dfg/nodes/system/ConvertToJson.py b/src/alert/controllers/api/dfg/nodes/system/ConvertToJson.py similarity index 95% rename from src/api/dfg/nodes/system/ConvertToJson.py rename to src/alert/controllers/api/dfg/nodes/system/ConvertToJson.py index eab2214..3894201 100644 --- a/src/api/dfg/nodes/system/ConvertToJson.py +++ b/src/alert/controllers/api/dfg/nodes/system/ConvertToJson.py @@ -1,6 +1,6 @@ from typing import Union -from api.datatype.Event import Event +from event.datatype.Event import Event from api.dfg.nodes.DfgNode import DfgNode from eatery.datatype.Eatery import Eatery diff --git a/src/api/dfg/nodes/system/DictResponseWrapper.py b/src/alert/controllers/api/dfg/nodes/system/DictResponseWrapper.py similarity index 100% rename from src/api/dfg/nodes/system/DictResponseWrapper.py rename to src/alert/controllers/api/dfg/nodes/system/DictResponseWrapper.py diff --git a/src/api/dfg/nodes/system/EateryGenerator.py b/src/alert/controllers/api/dfg/nodes/system/EateryGenerator.py similarity index 100% rename from src/api/dfg/nodes/system/EateryGenerator.py rename to src/alert/controllers/api/dfg/nodes/system/EateryGenerator.py diff --git a/src/api/dfg/nodes/system/InMemoryCache.py b/src/alert/controllers/api/dfg/nodes/system/InMemoryCache.py similarity index 100% rename from src/api/dfg/nodes/system/InMemoryCache.py rename to src/alert/controllers/api/dfg/nodes/system/InMemoryCache.py diff --git a/src/api/dfg/nodes/system/LeftMerge.py b/src/alert/controllers/api/dfg/nodes/system/LeftMerge.py similarity index 100% rename from src/api/dfg/nodes/system/LeftMerge.py rename to src/alert/controllers/api/dfg/nodes/system/LeftMerge.py diff --git a/src/api/dfg/nodes/system/Mapping.py b/src/alert/controllers/api/dfg/nodes/system/Mapping.py similarity index 100% rename from src/api/dfg/nodes/system/Mapping.py rename to src/alert/controllers/api/dfg/nodes/system/Mapping.py diff --git a/src/api/dfg/nodes/wait_times/WaitTimeFilter.py b/src/alert/controllers/api/dfg/nodes/wait_times/WaitTimeFilter.py similarity index 100% rename from src/api/dfg/nodes/wait_times/WaitTimeFilter.py rename to src/alert/controllers/api/dfg/nodes/wait_times/WaitTimeFilter.py diff --git a/src/api/dfg/nodes/wait_times/WaitTimes.py b/src/alert/controllers/api/dfg/nodes/wait_times/WaitTimes.py similarity index 99% rename from src/api/dfg/nodes/wait_times/WaitTimes.py rename to src/alert/controllers/api/dfg/nodes/wait_times/WaitTimes.py index 6279612..f98aeb2 100644 --- a/src/api/dfg/nodes/wait_times/WaitTimes.py +++ b/src/alert/controllers/api/dfg/nodes/wait_times/WaitTimes.py @@ -1,7 +1,7 @@ from datetime import date, timedelta import pytz -from api.datatype.Event import Event +from event.datatype.Event import Event from api.datatype.WaitTime import WaitTime from api.datatype.WaitTimesDay import WaitTimesDay from api.dfg.nodes.DfgNode import DfgNode diff --git a/src/api/management/commands/export_db_snapshot.py b/src/alert/controllers/api/management/commands/export_db_snapshot.py similarity index 100% rename from src/api/management/commands/export_db_snapshot.py rename to src/alert/controllers/api/management/commands/export_db_snapshot.py diff --git a/src/api/management/commands/ingest_db_snapshot.py b/src/alert/controllers/api/management/commands/ingest_db_snapshot.py similarity index 100% rename from src/api/management/commands/ingest_db_snapshot.py rename to src/alert/controllers/api/management/commands/ingest_db_snapshot.py diff --git a/src/api/management/commands/ingest_log_transactions.py b/src/alert/controllers/api/management/commands/ingest_log_transactions.py similarity index 100% rename from src/api/management/commands/ingest_log_transactions.py rename to src/alert/controllers/api/management/commands/ingest_log_transactions.py diff --git a/src/api/management/commands/ingest_recent_transactions.py b/src/alert/controllers/api/management/commands/ingest_recent_transactions.py similarity index 100% rename from src/api/management/commands/ingest_recent_transactions.py rename to src/alert/controllers/api/management/commands/ingest_recent_transactions.py diff --git a/src/api/migrations/0001_initial.py b/src/alert/controllers/api/migrations/0001_initial.py similarity index 100% rename from src/api/migrations/0001_initial.py rename to src/alert/controllers/api/migrations/0001_initial.py diff --git a/src/alert/controllers/api/migrations/0002_repeatingeventschedule_scheduleexception_and_more.py b/src/alert/controllers/api/migrations/0002_repeatingeventschedule_scheduleexception_and_more.py new file mode 100644 index 0000000..08a4d33 --- /dev/null +++ b/src/alert/controllers/api/migrations/0002_repeatingeventschedule_scheduleexception_and_more.py @@ -0,0 +1,111 @@ +# Generated by Django 4.0 on 2022-09-14 15:53 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import re + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0001_initial'), + ('api', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='RepeatingEventSchedule', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('event_description', models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], max_length=10)), + ('start_date', models.DateField()), + ('start_time', models.TimeField()), + ('end_time', models.TimeField()), + ('repeat_interval', models.IntegerField()), + ('offset_lst', models.CharField(max_length=100, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')])), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore')), + ], + ), + migrations.CreateModel( + name='ScheduleException', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('date', models.DateField()), + ('exception_type', models.CharField(choices=[('closed', 'Closed'), ('modified', 'Modified')], max_length=10)), + ('start_time', models.TimeField(blank=True, null=True)), + ('end_time', models.TimeField(blank=True, null=True)), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore')), + ('parent', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.repeatingeventschedule')), + ], + options={ + 'abstract': False, + }, + ), + migrations.RemoveField( + model_name='dateeventschedule', + name='eatery', + ), + migrations.RemoveField( + model_name='dateeventschedule', + name='menu', + ), + migrations.AlterUniqueTogether( + name='dayofweekeventschedule', + unique_together=None, + ), + migrations.RemoveField( + model_name='dayofweekeventschedule', + name='eatery', + ), + migrations.RemoveField( + model_name='dayofweekeventschedule', + name='menu', + ), + migrations.AlterField( + model_name='alertstore', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='itemstore', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='menustore', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='reportstore', + name='eatery', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.AlterField( + model_name='transactionhistorystore', + name='eatery', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), + ), + migrations.DeleteModel( + name='ClosedEventSchedule', + ), + migrations.DeleteModel( + name='DateEventSchedule', + ), + migrations.DeleteModel( + name='DayOfWeekEventSchedule', + ), + migrations.DeleteModel( + name='EateryStore', + ), + migrations.AddField( + model_name='repeatingeventschedule', + name='menu', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.menustore'), + ), + migrations.AlterUniqueTogether( + name='repeatingeventschedule', + unique_together={('eatery', 'event_description')}, + ), + ] diff --git a/src/api/migrations/__init__.py b/src/alert/controllers/api/migrations/__init__.py similarity index 100% rename from src/api/migrations/__init__.py rename to src/alert/controllers/api/migrations/__init__.py diff --git a/src/api/models/TransactionModel.py b/src/alert/controllers/api/models/TransactionModel.py similarity index 100% rename from src/api/models/TransactionModel.py rename to src/alert/controllers/api/models/TransactionModel.py diff --git a/src/alert/controllers/api/models/__init__.py b/src/alert/controllers/api/models/__init__.py new file mode 100644 index 0000000..3c045c1 --- /dev/null +++ b/src/alert/controllers/api/models/__init__.py @@ -0,0 +1,9 @@ +# Need to expose models to django. +# In this app, models should be imported directly from api.models, not from api.models.package + +from eatery.models import EateryStore + +#from .alert.models.AlertModel import AlertStore +#from .EventScheduleModel import EventSchedule, RepeatingEventSchedule, ScheduleException +#from ...reports.ReportModel import ReportStore +from .TransactionModel import TransactionHistoryStore diff --git a/src/api/serializers.py b/src/alert/controllers/api/serializers.py similarity index 88% rename from src/api/serializers.py rename to src/alert/controllers/api/serializers.py index 8fc5ff8..a7e9c09 100644 --- a/src/api/serializers.py +++ b/src/alert/controllers/api/serializers.py @@ -2,13 +2,6 @@ import api.models as models - -class AlertStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.AlertStore - fields = "__all__" - - class MenuStoreSerializer(serializers.ModelSerializer): class Meta: model = models.MenuStore @@ -38,7 +31,6 @@ class Meta: model = models.CategoryItemAssociation fields = "__all__" - class RepeatingEventScheduleSerializer(serializers.ModelSerializer): class Meta: model = models.RepeatingEventSchedule @@ -49,3 +41,5 @@ class ScheduleExceptionSerializer(serializers.ModelSerializer): class Meta: model = models.ScheduleException fields = "__all__" + + diff --git a/src/alert/controllers/misc_code.py b/src/alert/controllers/misc_code.py new file mode 100644 index 0000000..ee54407 --- /dev/null +++ b/src/alert/controllers/misc_code.py @@ -0,0 +1,27 @@ +# EateriesFromDB +"""alerts=EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts), + +@staticmethod + def alerts(eatery_id: int, serialized_alerts: list[dict]): + return [ + EateriesFromDB.alert_from_serialized(alert) + for alert in serialized_alerts + if alert["eatery"] == eatery_id + ] + + @staticmethod + def alert_from_serialized(serialized_alert: dict): + return EateryAlert( + id=serialized_alert["id"], + description=serialized_alert["description"], + start_timestamp=serialized_alert["start_timestamp"], + end_timestamp=serialized_alert["end_timestamp"], + + + alerts = AlertStore.objects.filter( + end_timestamp__gte=datetime.now().timestamp(), + start_timestamp__lte=datetime.now().timestamp(), + ) + serialized_alerts = AlertStoreSerializer(data=alerts, many=True) + serialized_alerts.is_valid() + )""" diff --git a/src/alert/migrations/0001_initial.py b/src/alert/migrations/0001_initial.py new file mode 100644 index 0000000..d9eca2b --- /dev/null +++ b/src/alert/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 4.0 on 2022-11-14 23:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('eatery', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Alert', + fields=[ + ('id', models.IntegerField(primary_key=True, serialize=False)), + ('description', models.CharField(max_length=250)), + ('start_timestamp', models.IntegerField()), + ('end_timestamp', models.IntegerField()), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eatery')), + ], + ), + ] diff --git a/src/alert/migrations/__init__.py b/src/alert/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/models/AlertModel.py b/src/alert/models.py similarity index 60% rename from src/api/models/AlertModel.py rename to src/alert/models.py index d8cc5de..f6df815 100644 --- a/src/api/models/AlertModel.py +++ b/src/alert/models.py @@ -1,10 +1,10 @@ from django.db import models -from eatery.models import EateryStore +from eatery.models import Eatery -class AlertStore(models.Model): +class Alert(models.Model): id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) + eatery = models.ForeignKey(Eatery, on_delete=models.DO_NOTHING) description = models.CharField(max_length = 250) start_timestamp = models.IntegerField() end_timestamp = models.IntegerField() diff --git a/src/alert/serializers.py b/src/alert/serializers.py new file mode 100644 index 0000000..8a178d1 --- /dev/null +++ b/src/alert/serializers.py @@ -0,0 +1,31 @@ +from wsgiref import validate +from rest_framework import serializers + +from models import Alert + +class AlertSerializer(serializers.ModelSerializer): + class Meta: + model = Alert + fields = "__all__" + + def create(self, validated_data): + """ + Create and return a new Alert. + """ + return Alert.objects.create(**validated_data) + + def update(self, instance, validated_data): + """ + Update an existing Alert. + """ + instance.description = validated_data.get('description', instance.description) + instance.start_timestamp = validated_data.get('start_timestamp',instance.start_timestamp) + instance.end_timestamp = validated_data.get('end_timestamp', instance.end_timestamp) + + instance.save() + return instance + +class AlertSubSerializer(serializers.ModelSerializer): + class Meta: + model = Alert + fields = ['id', 'description', 'start_timestamp', 'end_timestamp'] diff --git a/src/alert/urls.py b/src/alert/urls.py new file mode 100644 index 0000000..1723ae8 --- /dev/null +++ b/src/alert/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from alert.views import Alerts + +urlpatterns = [ + path("", Alerts.as_view(), name="get_all_alerts"), + path("/", Alerts.as_view(), name="get_eatery_alerts"), + path("/edit/", Alerts.as_view(), name="edit_eatery_alerts"), + path("/edit//", Alerts.as_view(), name="delete_eatery_alert") +] \ No newline at end of file diff --git a/src/alert/views.py b/src/alert/views.py new file mode 100644 index 0000000..292817b --- /dev/null +++ b/src/alert/views.py @@ -0,0 +1,30 @@ +from serializers import AlertSerializer +from models import Alert +from django.http import JsonResponse +from api.util.json import FieldType, error_json, success_json, verify_json_fields +from rest_framework.views import APIView +from rest_framework import viewsets + +""" +Basic CRUD functions: +- create an alert for eatery +- get alert for an eatery +- update an alert for an eatery +- delete alert for an eatery +""" +class AlertView(APIView): + model = Alert + + def get(self, request): + queryset = Alert.objects.all() + serializer = AlertSerializer(queryset, many=True) + return JsonResponse(serializer.data) + + def post(self, request): + pass + + def patch(self, request): + pass + + def delete(self, request): + pass \ No newline at end of file diff --git a/src/api/admin.py b/src/api/admin.py deleted file mode 100644 index 1a5cfa7..0000000 --- a/src/api/admin.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.contrib import admin - -import api.models as models - -# Register your models here. - -admin.site.register(models.MenuStore) -admin.site.register(models.ItemStore) -admin.site.register(models.SubItemStore) -admin.site.register(models.AlertStore) -admin.site.register(models.CategoryStore) -admin.site.register(models.CategoryItemAssociation) -admin.site.register(models.RepeatingEventSchedule) -admin.site.register(models.ScheduleException) -admin.site.register(models.TransactionHistoryStore) -admin.site.register(models.ReportStore) diff --git a/src/api/models/EventScheduleModel.py b/src/api/models/EventScheduleModel.py deleted file mode 100644 index 1280fed..0000000 --- a/src/api/models/EventScheduleModel.py +++ /dev/null @@ -1,53 +0,0 @@ -from enum import Enum - -from api.models.MenuModel import MenuStore -from django.core.validators import validate_comma_separated_integer_list -from django.db import models -from eatery.models import EateryStore - - -class EventDescription(models.TextChoices): - BREAKFAST = "Breakfast" - BRUNCH = "Brunch" - LUNCH = "Lunch" - DINNER = "Dinner" - GENERAL = "General" - - -class EventSchedule(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - - class Meta: - abstract = True - - -class RepeatingEventSchedule(EventSchedule): - - event_description = models.CharField( - choices=EventDescription.choices, max_length=10 - ) - menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - start_date = models.DateField() - start_time = models.TimeField() - end_time = models.TimeField() - repeat_interval = models.IntegerField() - offset_lst = models.CharField( - validators=[validate_comma_separated_integer_list], max_length=100 - ) - - class Meta: - unique_together = ("eatery", "event_description") - - -class ExceptionType(models.TextChoices): - CLOSED = "closed" - MODIFIED = "modified" - - -class ScheduleException(EventSchedule): - parent = models.ForeignKey(RepeatingEventSchedule, on_delete=models.DO_NOTHING) - date = models.DateField() - exception_type = models.CharField(max_length=10, choices=ExceptionType.choices) - start_time = models.TimeField(blank=True, null=True) - end_time = models.TimeField(blank=True, null=True) diff --git a/src/api/models/MenuModel.py b/src/api/models/MenuModel.py deleted file mode 100644 index 6d21c3c..0000000 --- a/src/api/models/MenuModel.py +++ /dev/null @@ -1,43 +0,0 @@ -from django.db import models -from eatery.models import EateryStore - - -class MenuStore(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - name = models.CharField(max_length=40) - - class Meta: - unique_together = ("eatery", "name") - - -class CategoryStore(models.Model): - id = models.IntegerField(primary_key=True) - menu = models.ForeignKey(MenuStore, on_delete=models.DO_NOTHING) - category = models.CharField(max_length=40) - - class Meta: - unique_together = ("menu", "category") - - -class ItemStore(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - name = models.CharField(max_length=40) - description = models.CharField(max_length=200, blank=True) - base_price = models.FloatField(null=True, blank=True) - - -class SubItemStore(models.Model): - id = models.IntegerField(primary_key=True) - item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) - additional_price = models.FloatField(null=True, blank=True) - total_price = models.FloatField(null=True, blank=True) - name = models.CharField(max_length=40) - item_subsection = models.CharField(max_length=40) - - -class CategoryItemAssociation(models.Model): - id = models.IntegerField(primary_key=True) - item = models.ForeignKey(ItemStore, on_delete=models.DO_NOTHING) - category = models.ForeignKey(CategoryStore, on_delete=models.DO_NOTHING) diff --git a/src/api/models/__init__.py b/src/api/models/__init__.py deleted file mode 100644 index c03ca98..0000000 --- a/src/api/models/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# Need to expose models to django. -# In this app, models should be imported directly from api.models, not from api.models.package - -from eatery.models import EateryStore - -from .AlertModel import AlertStore -from .EventScheduleModel import EventSchedule, RepeatingEventSchedule, ScheduleException -from .MenuModel import ( - CategoryItemAssociation, - CategoryStore, - ItemStore, - MenuStore, - SubItemStore, -) -from .ReportModel import ReportStore -from .TransactionModel import TransactionHistoryStore diff --git a/src/api/urls.py b/src/api/urls.py deleted file mode 100644 index d1d6989..0000000 --- a/src/api/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path -from eatery.views import EateryView - -from api.views import MainDfgView, ReportView - -urlpatterns = [ - path("", MainDfgView.as_view(), name="main"), - path("update", EateryView.as_view(), name="update"), - path("report", ReportView.as_view(), name="report"), -] diff --git a/src/category/__init__.py b/src/category/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/category/admin.py b/src/category/admin.py new file mode 100644 index 0000000..b6964bd --- /dev/null +++ b/src/category/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from category.models import Category + +admin.site.register(Category) diff --git a/src/category/apps.py b/src/category/apps.py new file mode 100644 index 0000000..1f46433 --- /dev/null +++ b/src/category/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CategoryConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'category' diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py new file mode 100644 index 0000000..daad41e --- /dev/null +++ b/src/category/controllers/populate_category.py @@ -0,0 +1,106 @@ +from category.models import Category +from category.serializers import CategorySerializer +from item.models import Item + +""" +Add categories to Category Model from CornellDiningNow. + +CornellDiningNow models cafe and dining hall categories differently, so they are parsed differently. +""" + +class PopulateCategoryController(): + def __init__(self): + self = self + + def generate_dining_hall_categories(self, json_event, menu): + """ + category_items = {"category_name" : id, ... } + """ + category_items = {} + for json_menu in json_event['menu']: + data = { + "menu" : int(menu.data['id']), + "category" : json_menu['category'] + } + category = CategorySerializer(data=data) + + if category.is_valid(): + category.save() + else: + print(category.errors) + + category_name = category.data['category'] + category_id = category.data['id'] + category_items[category_name] = category_id + + return category_items + + def generate_cafe_categories(self, json_eatery, menu): + """ + category_items = {"category_name" : id, ... } + """ + category_items = {} + categories = [] + dining_items = json_eatery["diningItems"] + + for item in dining_items: + if item["category"] not in categories: + categories.append(item["category"]) + data = { + "menu" : int(menu.data['id']), + "category" : item["category"] + } + category = CategorySerializer(data=data) + if category.is_valid(): + category.save() + else: + print(category.errors) + + category_name = category.data['category'] + category_id = category.data['id'] + category_items[category_name] = category_id + + return category_items + + def process(self, menus_dict, json_eateries): + """categories_dict = { eatery_id : + { menu[i] : {"category_name" : id, "category_name" : id}, + menu[i] : {"category_name" : id} + } + }""" + + categories_dict = {} + + for json_eatery in json_eateries: + eatery_id = int(json_eatery["id"]) + categories_dict[eatery_id] = {} + + if eatery_id in menus_dict: + eatery_menus = menus_dict[eatery_id]; i=0 + else: + continue + + is_cafe = "Cafe" in { + eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] + } + + """ + For every event in an eatery --> for every menu in an eatery --> get categories + """ + json_dates = json_eatery["operatingHours"] + for json_date in json_dates: + json_events = json_date["events"] + for json_event in json_events: + if i < len(eatery_menus): + menu = eatery_menus[i]; i += 1 + + categories_dict[eatery_id][menu.data['id']] = {} + + if is_cafe: + categories = self.generate_cafe_categories(json_eatery, menu) + else: + categories = self.generate_dining_hall_categories(json_event, menu) + + categories_dict[eatery_id][menu.data['id']] = categories + + return categories_dict \ No newline at end of file diff --git a/src/category/migrations/0001_initial.py b/src/category/migrations/0001_initial.py new file mode 100644 index 0000000..4d4964f --- /dev/null +++ b/src/category/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 4.0 on 2022-11-14 23:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('menu', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('category', models.CharField(default='General', max_length=40)), + ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='categories', to='menu.menu')), + ], + ), + ] diff --git a/src/category/migrations/0002_alter_category_menu.py b/src/category/migrations/0002_alter_category_menu.py new file mode 100644 index 0000000..551e819 --- /dev/null +++ b/src/category/migrations/0002_alter_category_menu.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0 on 2022-11-15 22:31 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('menu', '0001_initial'), + ('category', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='category', + name='menu', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='categories', to='menu.menu'), + ), + ] diff --git a/src/category/migrations/0003_alter_category_menu.py b/src/category/migrations/0003_alter_category_menu.py new file mode 100644 index 0000000..b3dad03 --- /dev/null +++ b/src/category/migrations/0003_alter_category_menu.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0 on 2022-11-15 22:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('menu', '0001_initial'), + ('category', '0002_alter_category_menu'), + ] + + operations = [ + migrations.AlterField( + model_name='category', + name='menu', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, related_name='categories', to='menu.menu'), + preserve_default=False, + ), + ] diff --git a/src/category/migrations/__init__.py b/src/category/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/category/models.py b/src/category/models.py new file mode 100644 index 0000000..dadc6c4 --- /dev/null +++ b/src/category/models.py @@ -0,0 +1,8 @@ +from django.db import models +from eatery.models import Eatery +from menu.models import Menu + +class Category(models.Model): + id = models.AutoField(primary_key=True) + menu = models.ForeignKey(Menu, related_name = 'categories', on_delete=models.DO_NOTHING) + category = models.CharField(max_length=40, default="General") diff --git a/src/category/serializers.py b/src/category/serializers.py new file mode 100644 index 0000000..a685969 --- /dev/null +++ b/src/category/serializers.py @@ -0,0 +1,17 @@ +from rest_framework import serializers +from category.models import Category +from item.serializers import ItemSerializer + + +class CategorySerializer(serializers.ModelSerializer): + id = serializers.IntegerField(read_only = True) + category = serializers.CharField(allow_null = True) + items = ItemSerializer(many=True, read_only=True) + + def create(self, validated_data): + category, _ = Category.objects.get_or_create(**validated_data) + return category + + class Meta: + model = Category + fields = ['id', 'category', 'menu', 'items'] \ No newline at end of file diff --git a/src/category/tests.py b/src/category/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/category/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/category/views.py b/src/category/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/src/category/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/cdn_parser/__init__.py b/src/cdn_parser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cdn_parser/admin.py b/src/cdn_parser/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/src/cdn_parser/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/cdn_parser/apps.py b/src/cdn_parser/apps.py new file mode 100644 index 0000000..4558e63 --- /dev/null +++ b/src/cdn_parser/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CdnParserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'cdn_parser' diff --git a/src/cdn_parser/controllers/filter_models.py b/src/cdn_parser/controllers/filter_models.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py new file mode 100644 index 0000000..307ffcc --- /dev/null +++ b/src/cdn_parser/controllers/populate_models.py @@ -0,0 +1,67 @@ +import requests +from eatery.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id + +from eatery.controllers.populate_eatery import PopulateEateryController +from event.controllers.populate_event import PopulateEventController +from menu.controllers.populate_menu import PopulateMenuController +from item.controllers.populate_item import PopulateItemController +from category.controllers.populate_category import PopulateCategoryController +""" +Parse through CornellDiningNow json to populate our eatery models. +""" + +class CornellDiningNowController(): + def __init__(self): + self = self + + def get_json(self): + try: + response = requests.get(CORNELL_DINING_URL) + except Exception as e: + raise e + if response.status_code <= 400: + response = response.json() + json_eateries = response["data"]["eateries"] + return json_eateries + + def process(self): + """ + 1. Get JSON from API + + 2. create eateries (fron CDN json) + + 3. create events (from CDN json) + return events_dict = { eatery_id : [event, event, event...], eatery_id : ... } + + 4. create menus for every eatery's events + return menus_dict = { eatery_id : [menu, menu, menu...] } + + 5. create categories in each menu + return categories_dict = + { eatery_id : + { menu[i] : {"category_name" : id, "category_name" : id...}, + menu[i] : {"category_name" : id...} + } + } + + 6. create items for each category + + """ + + json_eateries = self.get_json() + + PopulateEateryController().process(json_eateries) + + events_dict = PopulateEventController().process(json_eateries) + + menus_dict = PopulateMenuController().process(events_dict, json_eateries) + + categories_dict = PopulateCategoryController().process(menus_dict, json_eateries) + + PopulateItemController().process(categories_dict, json_eateries) + + + + + + diff --git a/src/cdn_parser/migrations/__init__.py b/src/cdn_parser/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cdn_parser/tests.py b/src/cdn_parser/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/cdn_parser/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/cdn_parser/urls.py b/src/cdn_parser/urls.py new file mode 100644 index 0000000..ea7f571 --- /dev/null +++ b/src/cdn_parser/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from cdn_parser.views import PopulateModels + +urlpatterns = [ + path("populate/", PopulateModels.as_view(), name="populate") +] \ No newline at end of file diff --git a/src/cdn_parser/views.py b/src/cdn_parser/views.py new file mode 100644 index 0000000..9e4815e --- /dev/null +++ b/src/cdn_parser/views.py @@ -0,0 +1,12 @@ +from django.shortcuts import render +from django.http import JsonResponse +from rest_framework import generics + +from eatery.util.json import FieldType, error_json, success_json, verify_json_fields +from cdn_parser.controllers.populate_models import CornellDiningNowController + +class PopulateModels(generics.GenericAPIView): + def get(self, request): + CornellDiningNowController().process() + return JsonResponse(success_json("Populated all models")) + diff --git a/src/eatery/admin.py b/src/eatery/admin.py index 6c379a7..3884336 100644 --- a/src/eatery/admin.py +++ b/src/eatery/admin.py @@ -1,7 +1,4 @@ from django.contrib import admin - import eatery.models as models -# Register your models here. - -admin.site.register(models.EateryStore) +admin.site.register(models.Eatery) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py new file mode 100644 index 0000000..02b980f --- /dev/null +++ b/src/eatery/controllers/populate_eatery.py @@ -0,0 +1,86 @@ +import requests +import json +from eatery.datatype.Eatery import EateryID +from eatery.util.constants import dining_id_to_internal_id, SnapshotFileName +from eatery.serializers import EaterySerializer +from eatery.models import Eatery + + +class PopulateEateryController: + def __init__(self): + self = self + + def generate_eatery(self, json_eatery): + """ + Create Eatery object from an eatery json from CornellDiningNow, and add to Eatery table. + """ + eatery_id = dining_id_to_internal_id(json_eatery["id"]).value + + data = { + "id" : eatery_id, + "name":json_eatery["name"], + "campus_area":json_eatery["campusArea"]["descrshort"], + "latitude":json_eatery["latitude"], + "longitude":json_eatery["longitude"], + "payment_accepts_cash":True, + "payment_accepts_brbs":any( + [ + method["descrshort"] == "Meal Plan - Debit" + for method in json_eatery["payMethods"] + ] + ), + "payment_accepts_meal_swipes":any( + [ + method["descrshort"] == "Meal Plan - Swipe" + for method in json_eatery["payMethods"] + ] + ), + "location":json_eatery["location"], + "online_order_url":json_eatery["onlineOrderUrl"] + } + + eatery = EaterySerializer(data=data) + if eatery.is_valid(): + eatery.save() + else: + print(eatery.errors) + + def add_eatery_store(self): + """ + Create eatery objects from an eatery json from a eatery_db_snapshot, and add to Eatery table. + """ + folder_path = "eatery/util/" + file_name = SnapshotFileName.EATERY_STORE + + with open(f"{folder_path}{file_name.value}", "r") as file: + json_objs = [] + for line in file: + if len(line) > 2: + json_objs.append(json.loads(line)) + + for json_obj in json_objs: + object = Eatery.objects.get(id=int(json_obj["id"])) + + if object.DoesNotExist: + """ + Create a new Eatery object + """ + serialized = EaterySerializer(data=json_obj) + else: + """ + Update already-existing Eatery object + """ + serialized = EaterySerializer(object, data=json_obj, partial=True) + + if serialized.is_valid(): + serialized.save() + else: + print(serialized.errors) + + + + def process(self, json_eateries): + for json_eatery in json_eateries: + self.generate_eatery(json_eatery) + + self.add_eatery_store() diff --git a/src/eatery/controllers/update_eatery.py b/src/eatery/controllers/update_eatery.py index b8f68b8..be31278 100644 --- a/src/eatery/controllers/update_eatery.py +++ b/src/eatery/controllers/update_eatery.py @@ -1,10 +1,10 @@ import base64 import os - import requests from django.http import QueryDict + from eatery.datatype.Eatery import EateryID -from eatery.models import EateryStore +from eatery.models import Eatery class UpdateEateryController: @@ -67,9 +67,17 @@ def upload_image(self, image): except: raise Exception("Image uploading unsuccessful") + """ + Pull new data from CornellDiningNow + >> left merge Eatery and CornellDiningNow + >> left merge Events and CornellDiningNow + """ + def compare(self): + pass + def process(self): """ Selects DB entry we want to update and updates it using provided data """ # use double-splat to convert dict to kwargs - EateryStore.objects.filter(id=self.id.value).update(**self.update_data) + Eatery.objects.filter(id=self.id.value).update(**self.update_data) diff --git a/src/eatery/datatype/Eatery.py b/src/eatery/datatype/Eatery.py index 95c979b..8117c28 100644 --- a/src/eatery/datatype/Eatery.py +++ b/src/eatery/datatype/Eatery.py @@ -3,9 +3,9 @@ from typing import Optional import pytz -from api.datatype.Event import Event, filter_range -from api.datatype.WaitTimesDay import WaitTimesDay -from eatery.datatype.EateryAlert import EateryAlert +#from event.datatype.Event import Event, filter_range +#from api.datatype.WaitTimesDay import WaitTimesDay +#from eatery.datatype.EateryAlert import EateryAlert class EateryID(Enum): @@ -47,113 +47,5 @@ class EateryID(Enum): LOUIES = 37 ANABELS_GROCERY = 38 MORRISON_DINING = 39 - NULL_EATERY = 41 + NOVICKS_CAFE = 40 - -class Eatery: - def __init__( - self, - id: EateryID, - name: Optional[str] = None, - image_url: Optional[str] = None, - menu_summary: Optional[str] = None, - campus_area: Optional[str] = None, - events: Optional[list[Event]] = None, - latitude: Optional[float] = None, - longitude: Optional[float] = None, - payment_accepts_cash: Optional[bool] = None, - payment_accepts_brbs: Optional[bool] = None, - payment_accepts_meal_swipes: Optional[bool] = None, - location: Optional[str] = None, - online_order_url: Optional[str] = None, - wait_times: Optional[list[WaitTimesDay]] = None, - alerts: Optional[list[EateryAlert]] = None, - ): - self.id = id - self.name = name - self.image_url = image_url - self.menu_summary = menu_summary - self.campus_area = campus_area - self.latitude = latitude - self.longitude = longitude - self.known_events = events - self.payment_accepts_cash = payment_accepts_cash - self.payment_accepts_brbs = payment_accepts_brbs - self.payment_accepts_meal_swipes = payment_accepts_meal_swipes - self.location = location - self.online_order_url = online_order_url - self.wait_times = wait_times - self.alerts = alerts - - def events( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None, - ): - return filter_range(self.known_events, tzinfo, start, end) - - def to_json( - self, - tzinfo: Optional[pytz.timezone] = None, - start: Optional[date] = None, - end: Optional[date] = None, - ): - eatery = { - "id": None if self.id is None else self.id.value, - "name": self.name, - "image_url": self.image_url, - "menu_summary": self.menu_summary, - "campus_area": self.campus_area, - "events": None - if self.known_events is None - else [event.to_json() for event in self.events(tzinfo, start, end)], - "latitude": self.latitude, - "longitude": self.longitude, - "payment_accepts_cash": self.payment_accepts_cash, - "payment_accepts_brbs": self.payment_accepts_brbs, - "payment_accepts_meal_swipes": self.payment_accepts_meal_swipes, - "location": self.location, - "online_order_url": self.online_order_url, - "wait_times": None - if self.wait_times is None - else [wait_time.to_json() for wait_time in self.wait_times], - "alerts": None - if self.alerts is None - else [alert.to_json() for alert in self.alerts], - } - return eatery - - @staticmethod - def from_json(eatery_json): - return Eatery( - id=None - if "id" not in eatery_json or eatery_json["id"] is None - else EateryID(eatery_json["id"]), - name=eatery_json.get("name"), - image_url=eatery_json.get("image_url"), - menu_summary=eatery_json.get("menu_summary"), - campus_area=eatery_json.get("campus_area"), - events=None - if "events" not in eatery_json or eatery_json["events"] is None - else [Event.from_json(event) for event in eatery_json["events"]], - latitude=eatery_json.get("latitude"), - longitude=eatery_json.get("longitude"), - payment_accepts_cash=eatery_json.get("payment_accepts_cash"), - payment_accepts_brbs=eatery_json.get("payment_accepts_brbs"), - payment_accepts_meal_swipes=eatery_json.get("payment_accepts_meal_swipes"), - location=eatery_json.get("location"), - online_order_url=eatery_json.get("online_order_url"), - wait_times=None - if "wait_times" not in eatery_json or eatery_json["wait_times"] is None - else [ - WaitTimesDay.from_json(day_wait_time) - for day_wait_time in eatery_json["wait_times"] - ], - alerts=None - if "alerts" not in eatery_json or eatery_json["alerts"] is None - else [EateryAlert.from_json(alert) for alert in eatery_json["alerts"]], - ) - - def clone(self): - return Eatery.from_json(self.to_json()) diff --git a/src/eatery/migrations/0001_initial.py b/src/eatery/migrations/0001_initial.py index cf6b3cb..daed3c9 100644 --- a/src/eatery/migrations/0001_initial.py +++ b/src/eatery/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2022-04-23 22:19 +# Generated by Django 4.0 on 2022-11-14 23:56 from django.db import migrations, models @@ -12,15 +12,15 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='EateryStore', + name='Eatery', fields=[ ('id', models.IntegerField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=40)), + ('name', models.CharField(max_length=40)), ('menu_summary', models.CharField(blank=True, max_length=60)), ('image_url', models.URLField(blank=True)), ('location', models.CharField(blank=True, max_length=30)), ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), - ('online_order_url', models.URLField(blank=True)), + ('online_order_url', models.URLField(blank=True, null=True)), ('latitude', models.FloatField(blank=True, null=True)), ('longitude', models.FloatField(blank=True, null=True)), ('payment_accepts_meal_swipes', models.BooleanField(blank=True, null=True)), diff --git a/src/eatery/migrations/0002_alter_eatery_menu_summary.py b/src/eatery/migrations/0002_alter_eatery_menu_summary.py new file mode 100644 index 0000000..dd0e8cf --- /dev/null +++ b/src/eatery/migrations/0002_alter_eatery_menu_summary.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2022-11-15 04:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='eatery', + name='menu_summary', + field=models.CharField(blank=True, default='', max_length=60, null=True), + ), + ] diff --git a/src/eatery/models.py b/src/eatery/models.py index 3d4c2ca..35a0bde 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -1,7 +1,7 @@ from django.db import models -class EateryStore(models.Model): +class Eatery(models.Model): class CampusArea(models.TextChoices): WEST = "West" NORTH = "North" @@ -10,14 +10,14 @@ class CampusArea(models.TextChoices): NONE = "" id = models.IntegerField(primary_key=True) - name = models.CharField(max_length=40, blank=True) - menu_summary = models.CharField(max_length=60, blank=True) + name = models.CharField(max_length=40) + menu_summary = models.CharField(max_length=60, blank=True, null=True, default="") image_url = models.URLField(blank=True) location = models.CharField(max_length=30, blank=True) campus_area = models.CharField( max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True ) - online_order_url = models.URLField(blank=True) + online_order_url = models.URLField(null=True, blank=True) latitude = models.FloatField(null=True, blank=True) longitude = models.FloatField(null=True, blank=True) payment_accepts_meal_swipes = models.BooleanField(null=True, blank=True) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 49e24bb..6cb95c6 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -1,9 +1,28 @@ from rest_framework import serializers +from eatery.models import Eatery +from event.serializers import EventSerializer -import eatery.models as models +class EaterySerializer(serializers.ModelSerializer): + id = serializers.IntegerField() + name = serializers.CharField() + menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") + image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + location = serializers.CharField(allow_null=True) + campus_area = serializers.CharField(allow_null=True) + online_order_url = serializers.URLField(allow_null=True) + latitude = serializers.FloatField(allow_null=True) + longitude = serializers.FloatField(allow_null=True) + payment_accepts_meal_swipes = serializers.BooleanField(allow_null=True) + payment_accepts_brbs = serializers.BooleanField(allow_null=True) + payment_accepts_cash = serializers.BooleanField(allow_null=True) + + events = EventSerializer(many=True, read_only=True) + + def create(self, validated_data): + eatery, _ = Eatery.objects.get_or_create(**validated_data) + return eatery -class EateryStoreSerializer(serializers.ModelSerializer): class Meta: - model = models.EateryStore - fields = "__all__" + model = Eatery + fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] diff --git a/src/eatery/urls.py b/src/eatery/urls.py new file mode 100644 index 0000000..71b9cf6 --- /dev/null +++ b/src/eatery/urls.py @@ -0,0 +1,19 @@ +from django.urls import path +from eatery.views import EateryViewSet + +eateries_list = EateryViewSet.as_view({ + 'get':'list', + 'post': 'create' +}) + +eatery_list = EateryViewSet.as_view({ + 'get':'retrieve', + 'put':'update', + 'patch':'partial_update', + 'delete':'destroy' +}) + +urlpatterns = [ + path("", eateries_list, name='eateries-list'), + path("/", eatery_list, name='eatery-list'), +] \ No newline at end of file diff --git a/src/api/util/constants.py b/src/eatery/util/constants.py similarity index 99% rename from src/api/util/constants.py rename to src/eatery/util/constants.py index 5e09f25..4db123f 100644 --- a/src/api/util/constants.py +++ b/src/eatery/util/constants.py @@ -38,8 +38,6 @@ def dining_id_to_internal_id(id: int): return EateryID.BUS_STOP_BAGELS elif id == 12: return EateryID.CAFE_JENNIE - elif id == 2: - return EateryID.CAROLS_CAFE elif id == 26: return EateryID.COOK_HOUSE elif id == 14: @@ -88,6 +86,8 @@ def dining_id_to_internal_id(id: int): return EateryID.TRILLIUM elif id == 43: return EateryID.MORRISON_DINING + elif id == 44: + return EateryID.NOVICKS_CAFE else: return None diff --git a/src/eatery/util/convert_from_json.py b/src/eatery/util/convert_from_json.py new file mode 100644 index 0000000..9134b41 --- /dev/null +++ b/src/eatery/util/convert_from_json.py @@ -0,0 +1,25 @@ +"""from event.datatype.Event import Event +from typing import Union +from api.dfg.nodes.DfgNode import DfgNode + + +class EventFromJson(DfgNode): + def __init__(self, child: DfgNode): + self.child = child + + def __call__(self, *args, **kwargs): + result = self.child(*args, **kwargs) + return EventFromJson.from_json(result, *args, **kwargs) + + def children(self): + return [self.child] + + @staticmethod + def from_json(obj: Union[list, dict], *args, **kwargs): + if isinstance(obj, list): + return [EventFromJson.from_json(elem, *args, **kwargs) for elem in obj] + else: + return Event.from_json(obj) + + def description(self): + return "ConvertFromJson""" \ No newline at end of file diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt new file mode 100644 index 0000000..bde75e7 --- /dev/null +++ b/src/eatery/util/eatery_store.txt @@ -0,0 +1,38 @@ +{"id": 1, "name": "", "menu_summary": "Halal food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 2, "name": "", "menu_summary": "Limes, corona, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 3, "name": "", "menu_summary": "Food, drink", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 4, "name": "", "menu_summary": "Quick bites and convenient food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 5, "name": "", "menu_summary": "Cleanest dining hall on campus", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 6, "name": "", "menu_summary": "Sandwiches, salads, hay", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 7, "name": "", "menu_summary": "Bagels, bagels, bagels", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 8, "name": "", "menu_summary": "Sandwiches, salads, potatoes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 9, "name": "", "menu_summary": "Carols, wine, christmas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Carols-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 10, "name": "", "menu_summary": "Well cooked food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 11, "name": "", "menu_summary": "Wraps, pizza, cookies", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 12, "name": "", "menu_summary": "Tea, pizza, wine", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 13, "name": "", "menu_summary": "Fries, burgers, and shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 14, "name": "", "menu_summary": "Sandwiches, salads, goldfish", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 15, "name": "", "menu_summary": "Fire, scales, green dragons", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 16, "name": "", "menu_summary": "Burgers, burritos, quesadillas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 17, "name": "", "menu_summary": "Ice cream, slushies, and fried dough", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 18, "name": "", "menu_summary": "Cookies, cereal, chicken pot pie", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 19, "name": "", "menu_summary": "Convenience items, milk", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 20, "name": "", "menu_summary": "Meat, salads, ice cream", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 21, "name": "", "menu_summary": "Sandwiches, soups, croissants", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 22, "name": "", "menu_summary": "Pollack specials, chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 23, "name": "", "menu_summary": "Sandwiches, soups, sushi", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 24, "name": "", "menu_summary": "Corn, vegetables, bread", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 25, "name": "", "menu_summary": "A historic dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 26, "name": "", "menu_summary": "The only central campus dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 27, "name": "", "menu_summary": "Vegan dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 28, "name": "", "menu_summary": "Sushi Fridays, freshly made pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/RPCC-Marketplace.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 29, "name": "", "menu_summary": "Taco tuesday, pizza, noodles", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 30, "name": "", "menu_summary": "Coffee, tea, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 31, "name": "", "menu_summary": "Falafel, rice, beans", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 32, "name": "", "menu_summary": "Fries, salads, fried chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 33, "name": "Terrace", "menu_summary": "Burrito and rice bowls, pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg", "location": "Statler Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.446267, "longitude": -76.482314, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true} +{"id": 34, "name": "Mac's Caf\u00e9", "menu_summary": "Flatbreads, salads, pasta", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/macs", "location": "Statler Hotel", "campus_area": "Central", "online_order_url": "", "latitude": 42.445921, "longitude": -76.481984, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true} +{"id": 35, "name": "Temple of Zeus", "menu_summary": "Coffee, pastries, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Zeus.jpg", "location": "Goldwin Smith Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.449091, "longitude": -76.483414, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} +{"id": 36, "name": "Gimme Coffee", "menu_summary": "Coffee, pastries, tea", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Gimme-Coffee.jpg", "location": "Gates Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.444958, "longitude": -76.481169, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} +{"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "online_order_url": "", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} +{"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} diff --git a/src/api/util/json.py b/src/eatery/util/json.py similarity index 100% rename from src/api/util/json.py rename to src/eatery/util/json.py diff --git a/src/api/util/time.py b/src/eatery/util/time.py similarity index 100% rename from src/api/util/time.py rename to src/eatery/util/time.py diff --git a/src/eatery/views.py b/src/eatery/views.py index 807e201..1c511a8 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -1,14 +1,52 @@ -from api.util.json import FieldType, error_json, success_json, verify_json_fields +from eatery.serializers import EaterySerializer +from eatery.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse -from django.shortcuts import render +from django.shortcuts import get_object_or_404 from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import viewsets from eatery.datatype.Eatery import EateryID +from eatery.models import Eatery + from .controllers.update_eatery import UpdateEateryController +class EateryViewSet(viewsets.ModelViewSet): + """ + View and edit eateries (all, or specific) + """ + queryset = Eatery.objects.all() + serializer_class = EaterySerializer + + def get_object(self): + queryset = self.filter_queryset(self.get_queryset()) + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field + + assert lookup_url_kwarg in self.kwargs, ( + 'Expected view %s to be called with a URL keyword argument ' + 'named "%s". Fix your URL conf, or set the `.lookup_field` ' + 'attribute on the view correctly.' % + (self.__class__.__name__, lookup_url_kwarg) + ) + # Uses the lookup_field attribute, which defaults to `pk` + filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} + obj = get_object_or_404(queryset, **filter_kwargs) + + # May raise a permission denied + self.check_object_permissions(self.request, obj) + return obj -class EateryView(APIView): +class GetEateries(APIView): + def get(self, request): + """ + Update models with CDN data + Update models with image information + Update models with + """ + return + +class UpdateEatery(APIView): def post(self, request): text_params = request.POST if not verify_json_fields( @@ -43,3 +81,12 @@ def post(self, request): return JsonResponse(success_json("Updated")) except Exception as e: return JsonResponse(error_json(str(e))) + + +class GetEateries(APIView): + def get(self, request): + eateries = EaterySerializer(Eatery.objects.all(), many=True) + if not eateries.data: + return JsonResponse(error_json("eateries is empty")) + + return JsonResponse(success_json(eateries.data)) diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 39a67c9..da8722c 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -38,8 +38,14 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "api", "eatery", + "alert", + "event", + "reports", + "menu", + "item", + "category", + "cdn_parser", "rest_framework", ] @@ -94,7 +100,7 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": BASE_DIR / "db.sqlite3", + "NAME": str(os.path.join(BASE_DIR, "db.sqlite3")), } } diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index eedc5d5..5b1abe9 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -17,6 +17,10 @@ from django.urls import include, path urlpatterns = [ - path("api/", include("api.urls")), path("admin/", admin.site.urls), + path("eatery/", include("eatery.urls")), + path("event/", include("event.urls")), + path("cdn/", include("cdn_parser.urls")) + #path("alert/", include("alert.urls")) + #path("wait_time/", include("wait_time.urls")) ] diff --git a/src/event/__init__.py b/src/event/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/event/admin.py b/src/event/admin.py new file mode 100644 index 0000000..645480e --- /dev/null +++ b/src/event/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from event.models import Event + +admin.site.register(Event) diff --git a/src/event/apps.py b/src/event/apps.py new file mode 100644 index 0000000..a52809c --- /dev/null +++ b/src/event/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class EventConfig(AppConfig): + name = "event" + diff --git a/src/api/dfg/nodes/schedule/ClosedSchedule.py b/src/event/controllers/ClosedSchedule.py similarity index 95% rename from src/api/dfg/nodes/schedule/ClosedSchedule.py rename to src/event/controllers/ClosedSchedule.py index d584585..8d7754b 100644 --- a/src/api/dfg/nodes/schedule/ClosedSchedule.py +++ b/src/event/controllers/ClosedSchedule.py @@ -1,5 +1,5 @@ from api.dfg.nodes.DfgNode import DfgNode -from api.models.EventScheduleModel import ExceptionType, ScheduleException +from event.EventScheduleModel import ExceptionType, ScheduleException from eatery.datatype.Eatery import Eatery, EateryID diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py new file mode 100644 index 0000000..6af6589 --- /dev/null +++ b/src/event/controllers/populate_event.py @@ -0,0 +1,70 @@ +from datetime import datetime +from event.serializers import EventSerializer +from eatery.util.constants import dining_id_to_internal_id + +class PopulateEventController(): + def __init__(self): + self = self + + def create_event_datetime(self, json_event, date): + """ + merge date and timestamp for creating event. + return {'start': start, 'end': end} + """ + start_time = datetime.fromtimestamp(json_event["startTimestamp"]) + end_time = datetime.fromtimestamp(json_event["endTimestamp"]) + start = datetime.combine(date, start_time.time()) + end = datetime.combine(date, end_time.time()) + + return {"start" : start, "end": end} + + + def generate_events(self, json_eatery): + """ + From an eatery json from CDN, create events for that eatery and add to event model. + """ + + #events = [ event obj, event, event ... ] for an eatery. + events = [] + + json_dates = json_eatery["operatingHours"] + for json_date in json_dates: + canon_date = datetime.fromisoformat(json_date["date"]) + json_events = json_date["events"] + + for json_event in json_events: + # Create an event: + dates = self.create_event_datetime(json_event, canon_date) + eatery_id = dining_id_to_internal_id(json_eatery["id"]).value + data = { + 'eatery': eatery_id, + 'event_description': json_event["descr"], + 'start' : dates['start'], + 'end' : dates['end']} + + event = EventSerializer(data=data) + + if event.is_valid(): + event.save() + else: + print(event.errors) + return event.errors + + events.append(event.data["id"]) + + + return events + + def process(self, json_eateries): + #events_dict { eatery_id : [event, event, event...], eatery_id : ... } + events_dict = {} + + for json_eatery in json_eateries: + eatery_id = int(json_eatery["id"]) + + events = self.generate_events(json_eatery) + events_dict[eatery_id] = events + + return events_dict + + \ No newline at end of file diff --git a/src/event/controllers/update_models/CornellDiningEvents.py b/src/event/controllers/update_models/CornellDiningEvents.py new file mode 100644 index 0000000..2ae6387 --- /dev/null +++ b/src/event/controllers/update_models/CornellDiningEvents.py @@ -0,0 +1,123 @@ +from datetime import date + +import requests +from event.datatype.Event import Event +from api.datatype.Menu import Menu +from api.datatype.MenuCategory import MenuCategory +from api.datatype.MenuItem import MenuItem +from api.dfg.nodes.DfgNode import DfgNode +from api.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id +from eatery.datatype.Eatery import Eatery, EateryID + + +class CornellDiningEvents(DfgNode): + def __init__(self, eatery_id: EateryID, cache): + self.eatery_id = eatery_id + self.cache = cache + + def __call__(self, *args, **kwargs) -> list[Eatery]: + if "eateries" not in self.cache: + try: + response = requests.get(CORNELL_DINING_URL).json() + except Exception as e: + raise e + if response["status"] == "success": + json_eateries = response["data"]["eateries"] + eateries = [] + for json_eatery in json_eateries: + eateries.append(CornellDiningEvents.parse_eatery(json_eatery)) + self.cache["eateries"] = eateries + for eatery in self.cache["eateries"]: + if eatery.id == self.eatery_id: + return eatery.events() + return [] + + @staticmethod + def parse_eatery(json_eatery: dict) -> Eatery: + is_cafe = "Cafe" in { + eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] + } + return Eatery( + id=dining_id_to_internal_id(json_eatery["id"]), + events=CornellDiningEvents.eatery_events_from_json( + json_operating_hours=json_eatery["operatingHours"], + json_dining_items=json_eatery["diningItems"], + is_cafe=is_cafe, + ), + ) + + @staticmethod + def eatery_events_from_json( + json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: + json_operating_hours = sorted( + json_operating_hours, key=lambda json_date_events: json_date_events["date"] + ) + events = [] + + for json_date_events in json_operating_hours: + canonical_date = date.fromisoformat(json_date_events["date"]) + + for json_event in json_date_events["events"]: + events.append( + Event( + canonical_date=canonical_date, + description=json_event["descr"], + start_timestamp=json_event["startTimestamp"], + end_timestamp=json_event["endTimestamp"], + menu=CornellDiningEvents.eatery_menu_from_json( + json_event["menu"], json_dining_items, is_cafe + ), + ) + ) + + return events + + @staticmethod + def eatery_menu_from_json(json_menu: list, json_dining_items: list, is_cafe: bool): + if is_cafe: + return CornellDiningEvents.cafe_menu_from_json(json_dining_items) + else: + return CornellDiningEvents.dining_hall_menu_from_json(json_menu) + + @staticmethod + def cafe_menu_from_json(json_dining_items: list) -> Menu: + category_map = {} + for item in json_dining_items: + if item["category"] not in category_map: + category_map[item["category"]] = [] + category_map[item["category"]].append( + MenuItem(healthy=item["healthy"], name=item["item"]) + ) + categories = [] + for category_name in category_map: + categories.append(MenuCategory(category_name, category_map[category_name])) + return Menu(categories=categories) + + @staticmethod + def dining_hall_menu_from_json(json_menu: list) -> Menu: + json_menu = sorted( + json_menu, key=lambda json_menu_category: json_menu_category["sortIdx"] + ) + menu_categories = [] + + for json_menu_category in json_menu: + items = [ + CornellDiningEvents.from_cornell_dining_json(json_item) + for json_item in json_menu_category["items"] + ] + + menu_categories.append( + MenuCategory(category=json_menu_category["category"], items=items) + ) + + return Menu(categories=menu_categories) + + @staticmethod + def from_cornell_dining_json(json_item: dict): + return MenuItem( + healthy=json_item["healthy"], + name=json_item["item"] + ) + + def description(self): + return "CornellDiningEvents" diff --git a/src/event/controllers/update_models/CornellDiningNow.py b/src/event/controllers/update_models/CornellDiningNow.py new file mode 100644 index 0000000..98693c7 --- /dev/null +++ b/src/event/controllers/update_models/CornellDiningNow.py @@ -0,0 +1,52 @@ +import requests +from api.dfg.nodes.DfgNode import DfgNode +from api.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id +from eatery.datatype.Eatery import Eatery + + +class CornellDiningNow(DfgNode): + def __call__(self, *args, **kwargs) -> list[Eatery]: + try: + response = requests.get(CORNELL_DINING_URL).json() + + except Exception as e: + raise e + + if response["status"] == "success": + json_eateries = response["data"]["eateries"] + eateries = [] + for json_eatery in json_eateries: + eateries.append(CornellDiningNow.parse_eatery(json_eatery)) + return eateries + + else: + raise Exception(response["message"]) + + @staticmethod + def parse_eatery(json_eatery: dict) -> Eatery: + # Events are parsed later + return Eatery( + id=dining_id_to_internal_id(json_eatery["id"]), + name=json_eatery["name"], + campus_area=json_eatery["campusArea"]["descrshort"], + latitude=json_eatery["latitude"], + longitude=json_eatery["longitude"], + payment_accepts_cash=True, + payment_accepts_brbs=any( + [ + method["descrshort"] == "Meal Plan - Debit" + for method in json_eatery["payMethods"] + ] + ), + payment_accepts_meal_swipes=any( + [ + method["descrshort"] == "Meal Plan - Swipe" + for method in json_eatery["payMethods"] + ] + ), + location=json_eatery["location"], + online_order_url=json_eatery["onlineOrderUrl"], + ) + + def description(self): + return "CornellDiningNow" diff --git a/src/api/dfg/nodes/schedule/CacheMenuInjection.py b/src/event/controllers/update_models/schedule/CacheMenuInjection.py similarity index 100% rename from src/api/dfg/nodes/schedule/CacheMenuInjection.py rename to src/event/controllers/update_models/schedule/CacheMenuInjection.py diff --git a/src/api/dfg/nodes/schedule/CornellDiningEvents.py b/src/event/controllers/update_models/schedule/CornellDiningEvents.py similarity index 93% rename from src/api/dfg/nodes/schedule/CornellDiningEvents.py rename to src/event/controllers/update_models/schedule/CornellDiningEvents.py index c6cf812..7101169 100644 --- a/src/api/dfg/nodes/schedule/CornellDiningEvents.py +++ b/src/event/controllers/update_models/schedule/CornellDiningEvents.py @@ -1,7 +1,7 @@ from datetime import date import requests -from api.datatype.Event import Event +from event.datatype.Event import Event from api.datatype.Menu import Menu from api.datatype.MenuCategory import MenuCategory from api.datatype.MenuItem import MenuItem @@ -103,7 +103,7 @@ def dining_hall_menu_from_json(json_menu: list) -> Menu: for json_menu_category in json_menu: items = [ - MenuItem.from_cornell_dining_json(json_item) + CornellDiningEvents.from_cornell_dining_json(json_item) for json_item in json_menu_category["items"] ] @@ -113,5 +113,12 @@ def dining_hall_menu_from_json(json_menu: list) -> Menu: return Menu(categories=menu_categories) + @staticmethod + def from_cornell_dining_json(json_item: dict): + return MenuItem( + healthy=json_item["healthy"], + name=json_item["item"] + ) + def description(self): return "CornellDiningEvents" diff --git a/src/api/dfg/nodes/schedule/ModifiedSchedules.py b/src/event/controllers/update_models/schedule/ModifiedSchedules.py similarity index 94% rename from src/api/dfg/nodes/schedule/ModifiedSchedules.py rename to src/event/controllers/update_models/schedule/ModifiedSchedules.py index 12b6a2c..accb793 100644 --- a/src/api/dfg/nodes/schedule/ModifiedSchedules.py +++ b/src/event/controllers/update_models/schedule/ModifiedSchedules.py @@ -1,8 +1,8 @@ from datetime import timedelta -from api.datatype.Event import Event +from event.datatype.Event import Event from api.dfg.nodes.DfgNode import DfgNode -from api.models.EventScheduleModel import ExceptionType, ScheduleException +from event.models.EventScheduleModel import ExceptionType, ScheduleException from api.util.time import combined_timestamp from eatery.datatype.Eatery import Eatery, EateryID diff --git a/src/api/dfg/nodes/schedule/RepeatingSchedule.py b/src/event/controllers/update_models/schedule/RepeatingSchedule.py similarity index 98% rename from src/api/dfg/nodes/schedule/RepeatingSchedule.py rename to src/event/controllers/update_models/schedule/RepeatingSchedule.py index 5cbfcb0..6b27a8a 100644 --- a/src/api/dfg/nodes/schedule/RepeatingSchedule.py +++ b/src/event/controllers/update_models/schedule/RepeatingSchedule.py @@ -1,6 +1,6 @@ from datetime import timedelta -from api.datatype.Event import Event +from event.datatype.Event import Event from api.dfg.nodes.DfgNode import DfgNode from api.models import RepeatingEventSchedule from api.util.time import combined_timestamp diff --git a/src/api/datatype/Event.py b/src/event/datatype/Event.py similarity index 97% rename from src/api/datatype/Event.py rename to src/event/datatype/Event.py index e0d1b7b..02e8024 100644 --- a/src/api/datatype/Event.py +++ b/src/event/datatype/Event.py @@ -2,14 +2,13 @@ from typing import Optional import pytz -from api.datatype.Menu import Menu -from api.util.time import combined_timestamp +from datatype.Menu import Menu +#from api.util.time import combined_timestamp class Event: """ Represents an event on a particular date - Attributes ---------- description @@ -115,4 +114,4 @@ def filter_range( else: raise Exception( f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}" - ) + ) \ No newline at end of file diff --git a/src/api/datatype/Menu.py b/src/event/datatype/Menu.py similarity index 87% rename from src/api/datatype/Menu.py rename to src/event/datatype/Menu.py index c48e100..118d378 100644 --- a/src/api/datatype/Menu.py +++ b/src/event/datatype/Menu.py @@ -1,4 +1,4 @@ -from api.datatype.MenuCategory import MenuCategory +from datatype.MenuCategory import MenuCategory class Menu: diff --git a/src/api/datatype/MenuCategory.py b/src/event/datatype/MenuCategory.py similarity index 100% rename from src/api/datatype/MenuCategory.py rename to src/event/datatype/MenuCategory.py diff --git a/src/api/datatype/MenuItem.py b/src/event/datatype/MenuItem.py similarity index 87% rename from src/api/datatype/MenuItem.py rename to src/event/datatype/MenuItem.py index b611652..34124db 100644 --- a/src/api/datatype/MenuItem.py +++ b/src/event/datatype/MenuItem.py @@ -4,13 +4,6 @@ class MenuItem: - @staticmethod - def from_cornell_dining_json(json_item: dict): - return MenuItem( - healthy=json_item["healthy"], - name=json_item["item"] - ) - def __init__( self, name: str, diff --git a/src/api/datatype/MenuItemSection.py b/src/event/datatype/MenuItemSection.py similarity index 100% rename from src/api/datatype/MenuItemSection.py rename to src/event/datatype/MenuItemSection.py diff --git a/src/api/datatype/MenuSubItem.py b/src/event/datatype/MenuSubItem.py similarity index 100% rename from src/api/datatype/MenuSubItem.py rename to src/event/datatype/MenuSubItem.py diff --git a/src/api/datatype/WaitTime.py b/src/event/datatype/WaitTime.py similarity index 100% rename from src/api/datatype/WaitTime.py rename to src/event/datatype/WaitTime.py diff --git a/src/api/datatype/WaitTimesDay.py b/src/event/datatype/WaitTimesDay.py similarity index 100% rename from src/api/datatype/WaitTimesDay.py rename to src/event/datatype/WaitTimesDay.py diff --git a/src/event/migrations/0001_initial.py b/src/event/migrations/0001_initial.py new file mode 100644 index 0000000..29ba406 --- /dev/null +++ b/src/event/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 4.0 on 2022-11-14 23:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('eatery', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('event_description', models.CharField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', max_length=10, null=True)), + ('start', models.DateTimeField(auto_now_add=True)), + ('end', models.DateTimeField(auto_now_add=True)), + ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='events', to='eatery.eatery')), + ], + ), + ] diff --git a/src/event/migrations/0002_alter_event_end_alter_event_start.py b/src/event/migrations/0002_alter_event_end_alter_event_start.py new file mode 100644 index 0000000..cc44cc9 --- /dev/null +++ b/src/event/migrations/0002_alter_event_end_alter_event_start.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0 on 2022-11-15 05:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='end', + field=models.DateTimeField(), + ), + migrations.AlterField( + model_name='event', + name='start', + field=models.DateTimeField(), + ), + ] diff --git a/src/event/migrations/__init__.py b/src/event/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/event/models.py b/src/event/models.py new file mode 100644 index 0000000..921302a --- /dev/null +++ b/src/event/models.py @@ -0,0 +1,20 @@ +from django.db import models + +from eatery.models import Eatery + +class EventDescription(models.TextChoices): + BREAKFAST = "Breakfast" + BRUNCH = "Brunch" + LUNCH = "Lunch" + DINNER = "Dinner" + GENERAL = "General" + CAFE = "Cafe" + PANTS = "Pants" + +class Event(models.Model): + id = models.AutoField(primary_key=True) + eatery = models.ForeignKey(Eatery, related_name = "events", on_delete=models.DO_NOTHING) + event_description = models.CharField( + choices=EventDescription.choices, max_length=10, default = EventDescription.GENERAL, blank=True, null = True) + start = models.DateTimeField() + end = models.DateTimeField() diff --git a/src/event/serializers.py b/src/event/serializers.py new file mode 100644 index 0000000..eb7d981 --- /dev/null +++ b/src/event/serializers.py @@ -0,0 +1,19 @@ +from rest_framework import serializers +from event.models import Event +from menu.serializers import MenuSerializer + +class EventSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False, read_only = True) + event_description = serializers.CharField(allow_null=True, allow_blank = True, default=None) + start = serializers.DateTimeField() + end = serializers.DateTimeField() + + menus = MenuSerializer(many=True, read_only=True) + + def create(self, validated_data): + event, _ = Event.objects.get_or_create(**validated_data) + return event + + class Meta: + model = Event + fields = ['id', 'eatery', 'event_description', 'start', 'end', 'menus'] \ No newline at end of file diff --git a/src/event/urls.py b/src/event/urls.py new file mode 100644 index 0000000..ce94074 --- /dev/null +++ b/src/event/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from event.views import PopulateEventView + +urlpatterns = [ + path("populate/", PopulateEventView.as_view(), name="menu"), +] diff --git a/src/event/views.py b/src/event/views.py new file mode 100644 index 0000000..24e582a --- /dev/null +++ b/src/event/views.py @@ -0,0 +1,13 @@ +import json +from datetime import date, timedelta + +import pytz +from django.http import JsonResponse +from eatery.datatype.Eatery import EateryID +from rest_framework.views import APIView +from rest_framework import generics + +from eatery.util.json import FieldType, error_json, success_json, verify_json_fields + +class PopulateEventView(generics.GenericAPIView): + pass \ No newline at end of file diff --git a/src/item/__init__.py b/src/item/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/item/admin.py b/src/item/admin.py new file mode 100644 index 0000000..961cd89 --- /dev/null +++ b/src/item/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from item.models import Item + +admin.site.register(Item) diff --git a/src/api/apps.py b/src/item/apps.py similarity index 66% rename from src/api/apps.py rename to src/item/apps.py index 66656fd..f290658 100644 --- a/src/api/apps.py +++ b/src/item/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class ApiConfig(AppConfig): +class ItemConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'api' + name = 'item' diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py new file mode 100644 index 0000000..d892bf8 --- /dev/null +++ b/src/item/controllers/populate_item.py @@ -0,0 +1,72 @@ +from item.models import Item +from item.serializers import ItemSerializer +from eatery.models import Eatery +from eatery.serializers import EaterySerializer +import string + +class PopulateItemController(): + def __init__(self): + self = self + + def generate_cafe_items(self, menu, json_eatery): + + for json_item in json_eatery["diningItems"]: + + category_name = json_item['category'].strip() + category_id = menu[category_name] + + data = { + "category" : category_id, + "name" : json_item["item"] + } + item = ItemSerializer(data=data) + if item.is_valid(): + item.save() + else: + print(item.errors) + + + def generate_dining_hall_items(self, menu, json_event, json_eatery): + json_menus = json_event['menu'] + for json_menu in json_menus: + + category_name = json_menu['category'].strip() + category_id = menu[category_name] + + for json_item in json_menu['items']: + data = { + "category" : category_id, + "name" : json_item["item"] + } + item = ItemSerializer(data=data) + if item.is_valid(): + item.save() + else: + print(item.errors) + + def process(self, categories_dict, json_eateries): + for json_eatery in json_eateries: + if int(json_eatery["id"]) in categories_dict: + eatery_menus = categories_dict[int(json_eatery["id"])] + else: + continue + + iter = list(eatery_menus.keys()) + i = 0 + + is_cafe = "Cafe" in { + eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] + } + + json_dates = json_eatery["operatingHours"] + for json_date in json_dates: + json_events = json_date["events"] + for json_event in json_events: + if i < len(iter): + menu_id = iter[i] + menu = eatery_menus[menu_id]; i += 1 + + if is_cafe: + self.generate_cafe_items(menu, json_eatery) + else: + self.generate_dining_hall_items(menu, json_event, json_eatery) diff --git a/src/item/migrations/0001_initial.py b/src/item/migrations/0001_initial.py new file mode 100644 index 0000000..28b39e0 --- /dev/null +++ b/src/item/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0 on 2022-11-14 23:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('category', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Item', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(default='Item', max_length=40)), + ('base_price', models.FloatField(blank=True, null=True)), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='items', to='category.category')), + ], + ), + ] diff --git a/src/item/migrations/0002_alter_item_base_price.py b/src/item/migrations/0002_alter_item_base_price.py new file mode 100644 index 0000000..299f09a --- /dev/null +++ b/src/item/migrations/0002_alter_item_base_price.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2022-11-15 23:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('item', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='item', + name='base_price', + field=models.FloatField(blank=True, default=0.0, null=True), + ), + ] diff --git a/src/item/migrations/__init__.py b/src/item/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/item/models.py b/src/item/models.py new file mode 100644 index 0000000..a2b3fa4 --- /dev/null +++ b/src/item/models.py @@ -0,0 +1,9 @@ +from django.db import models +from eatery.models import Eatery +from category.models import Category + +class Item(models.Model): + id = models.AutoField(primary_key=True) + category = models.ForeignKey(Category, related_name = "items", on_delete=models.DO_NOTHING) + name = models.CharField(max_length=40, default = "Item") + base_price = models.FloatField(null=True, blank=True, default=0.0) diff --git a/src/item/serializers.py b/src/item/serializers.py new file mode 100644 index 0000000..772bf4c --- /dev/null +++ b/src/item/serializers.py @@ -0,0 +1,14 @@ +from rest_framework import serializers +from item.models import Item + +class ItemSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(read_only = True) + name = serializers.CharField(default = "Item") + + def create(self, validated_data): + item, _ = Item.objects.get_or_create(**validated_data) + return item + + class Meta: + model = Item + fields = ['id', 'category', 'name'] \ No newline at end of file diff --git a/src/item/tests.py b/src/item/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/item/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/item/views.py b/src/item/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/src/item/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/menu/__init__.py b/src/menu/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/menu/admin.py b/src/menu/admin.py new file mode 100644 index 0000000..e8ac683 --- /dev/null +++ b/src/menu/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from menu.models import Menu + +admin.site.register(Menu) \ No newline at end of file diff --git a/src/menu/apps.py b/src/menu/apps.py new file mode 100644 index 0000000..88512e8 --- /dev/null +++ b/src/menu/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MenuConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'menu' diff --git a/src/menu/controllers/populate_menu.py b/src/menu/controllers/populate_menu.py new file mode 100644 index 0000000..579bb93 --- /dev/null +++ b/src/menu/controllers/populate_menu.py @@ -0,0 +1,42 @@ +from menu.serializers import MenuSerializer + +class PopulateMenuController(): + def __init__(self): + self = self + menus = [] + + def generate_menu(self, json_event, event): + data = { + "event" : event #int(event.data["id"]) + } + menu = MenuSerializer(data=data) + if menu.is_valid(): + menu.save() + else: + print(menu.errors) + return menu + + + + def process(self, events_dict, json_eateries): + """ + menus_dict = { eatery_id : [menu, menu, menu...] } + """ + menus_dict = {} + + for json_eatery in json_eateries: + eatery_events = events_dict[int(json_eatery["id"])]; i=0 + json_dates = json_eatery["operatingHours"] + + menus_dict[int(json_eatery["id"])] = [] + + for json_date in json_dates: + json_events = json_date["events"] + for json_event in json_events: + event = eatery_events[i]; i+=1 + menu = self.generate_menu(json_event, event) + menus_dict[int(json_eatery["id"])].append(menu) + + return menus_dict + + diff --git a/src/menu/migrations/0001_initial.py b/src/menu/migrations/0001_initial.py new file mode 100644 index 0000000..89d16f8 --- /dev/null +++ b/src/menu/migrations/0001_initial.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0 on 2022-11-14 23:56 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('event', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Menu', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='menus', to='event.event')), + ], + ), + ] diff --git a/src/menu/migrations/__init__.py b/src/menu/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/menu/models.py b/src/menu/models.py new file mode 100644 index 0000000..f35fd4d --- /dev/null +++ b/src/menu/models.py @@ -0,0 +1,6 @@ +from django.db import models +from event.models import Event + +class Menu(models.Model): + id = models.AutoField(primary_key=True) + event = models.ForeignKey(Event, related_name="menus", on_delete = models.DO_NOTHING) diff --git a/src/menu/serializers.py b/src/menu/serializers.py new file mode 100644 index 0000000..1a397dd --- /dev/null +++ b/src/menu/serializers.py @@ -0,0 +1,15 @@ +from rest_framework import serializers +from menu.models import Menu +from category.serializers import CategorySerializer + +class MenuSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(read_only = True) + categories = CategorySerializer(many=True, read_only=True) + + def create(self, validated_data): + menu, _ = Menu.objects.get_or_create(**validated_data) + return menu + + class Meta: + model = Menu + fields = ['id', 'event', 'categories'] \ No newline at end of file diff --git a/src/menu/tests.py b/src/menu/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/menu/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/menu/views.py b/src/menu/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/src/menu/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/reports/__init__.py b/src/reports/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/reports/admin.py b/src/reports/admin.py new file mode 100644 index 0000000..d590982 --- /dev/null +++ b/src/reports/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from reports.models import ReportStore + +admin.site.register(ReportStore) diff --git a/src/reports/apps.py b/src/reports/apps.py new file mode 100644 index 0000000..5c787a3 --- /dev/null +++ b/src/reports/apps.py @@ -0,0 +1,4 @@ +from django.apps import AppConfig + +class ReportConfig(AppConfig): + name = "reports" \ No newline at end of file diff --git a/src/api/controllers/create_report.py b/src/reports/controllers/create_report.py similarity index 100% rename from src/api/controllers/create_report.py rename to src/reports/controllers/create_report.py diff --git a/src/api/models/ReportModel.py b/src/reports/models.py similarity index 60% rename from src/api/models/ReportModel.py rename to src/reports/models.py index 86ab329..070a17e 100644 --- a/src/api/models/ReportModel.py +++ b/src/reports/models.py @@ -1,9 +1,9 @@ from django.db import models -from eatery.models import EateryStore +from eatery.models import Eatery class ReportStore(models.Model): - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING, null=True) + eatery = models.ForeignKey(Eatery, on_delete=models.DO_NOTHING, null=True) type = models.CharField(max_length=200) content = models.TextField() created_timestamp = models.IntegerField() diff --git a/src/reports/urls.py b/src/reports/urls.py new file mode 100644 index 0000000..652cad1 --- /dev/null +++ b/src/reports/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from views import ReportView + +urlpatterns = [ + path("", ReportView.as_view(), name="main"), + """ + path("delete", ReportView.as_view(),name="delete"), + path("post", ReportView.as_view(), name="post") + """ +] \ No newline at end of file diff --git a/src/api/views.py b/src/reports/views.py similarity index 61% rename from src/api/views.py rename to src/reports/views.py index 6d3ee65..8b74c75 100644 --- a/src/api/views.py +++ b/src/reports/views.py @@ -1,33 +1,11 @@ import json -from datetime import date, timedelta - -import pytz from django.http import JsonResponse from eatery.datatype.Eatery import EateryID from rest_framework.views import APIView -from api.controllers.create_report import CreateReportController -from api.dfg.main import main_dfg +from reports.controllers.create_report import CreateReportController from api.util.json import FieldType, error_json, success_json, verify_json_fields -# Create your views here. - - -class MainDfgView(APIView): - dfg = main_dfg - - def get(self, request): - tzinfo = pytz.timezone("US/Eastern") - reload = request.GET.get("reload") - result = self.dfg( - tzinfo=tzinfo, - reload=reload is not None and reload != "false", - start=date.today(), - end=date.today() + timedelta(days=7), - ) - return JsonResponse(result) - - class ReportView(APIView): def post(self, request): json_body = json.loads(request.body) From 28397e70420781133be2cb9ee01deddbc0b4d387 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 22 Feb 2023 17:43:23 -0500 Subject: [PATCH 107/305] fix /cdn/poplulate endpoint bug --- .gitignore | 3 +- src/eatery/controllers/populate_eatery.py | 44 +++++++++++------------ src/eatery_blue_backend/urls.py | 5 +-- start.sh | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 54927b1..00e0e79 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,8 @@ __pycache__/ # More Python stuff .DS_Store .vscode/ -env/ +env/ +venv/ .Python build/ develop-eggs/ diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index 02b980f..20e53cb 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -1,4 +1,4 @@ -import requests +import requests import json from eatery.datatype.Eatery import EateryID from eatery.util.constants import dining_id_to_internal_id, SnapshotFileName @@ -9,7 +9,7 @@ class PopulateEateryController: def __init__(self): self = self - + def generate_eatery(self, json_eatery): """ Create Eatery object from an eatery json from CornellDiningNow, and add to Eatery table. @@ -17,26 +17,26 @@ def generate_eatery(self, json_eatery): eatery_id = dining_id_to_internal_id(json_eatery["id"]).value data = { - "id" : eatery_id, - "name":json_eatery["name"], - "campus_area":json_eatery["campusArea"]["descrshort"], - "latitude":json_eatery["latitude"], - "longitude":json_eatery["longitude"], - "payment_accepts_cash":True, - "payment_accepts_brbs":any( + "id": eatery_id, + "name": json_eatery["name"], + "campus_area": json_eatery["campusArea"]["descrshort"], + "latitude": json_eatery["latitude"], + "longitude": json_eatery["longitude"], + "payment_accepts_cash": True, + "payment_accepts_brbs": any( [ method["descrshort"] == "Meal Plan - Debit" for method in json_eatery["payMethods"] ] ), - "payment_accepts_meal_swipes":any( + "payment_accepts_meal_swipes": any( [ method["descrshort"] == "Meal Plan - Swipe" for method in json_eatery["payMethods"] ] ), - "location":json_eatery["location"], - "online_order_url":json_eatery["onlineOrderUrl"] + "location": json_eatery["location"], + "online_order_url": json_eatery["onlineOrderUrl"], } eatery = EaterySerializer(data=data) @@ -44,12 +44,12 @@ def generate_eatery(self, json_eatery): eatery.save() else: print(eatery.errors) - - def add_eatery_store(self): + + def add_eatery_store(self): """ Create eatery objects from an eatery json from a eatery_db_snapshot, and add to Eatery table. """ - folder_path = "eatery/util/" + folder_path = "src/eatery/util/" file_name = SnapshotFileName.EATERY_STORE with open(f"{folder_path}{file_name.value}", "r") as file: @@ -59,14 +59,14 @@ def add_eatery_store(self): json_objs.append(json.loads(line)) for json_obj in json_objs: - object = Eatery.objects.get(id=int(json_obj["id"])) - - if object.DoesNotExist: - """ + try: + object = Eatery.objects.get(id=int(json_obj["id"])) + except object.DoesNotExist: + """ Create a new Eatery object """ serialized = EaterySerializer(data=json_obj) - else: + else: """ Update already-existing Eatery object """ @@ -77,10 +77,8 @@ def add_eatery_store(self): else: print(serialized.errors) - - def process(self, json_eateries): - for json_eatery in json_eateries: + for json_eatery in json_eateries: self.generate_eatery(json_eatery) self.add_eatery_store() diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 5b1abe9..9a49daf 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -21,6 +21,7 @@ path("eatery/", include("eatery.urls")), path("event/", include("event.urls")), path("cdn/", include("cdn_parser.urls")) - #path("alert/", include("alert.urls")) - #path("wait_time/", include("wait_time.urls")) + # path("alert/", include("alert.urls")) + # path("reports/", include("reports.urls")) + # path("wait_time/", include("wait_time.urls")) ] diff --git a/start.sh b/start.sh index 8994c63..bdcdbf5 100644 --- a/start.sh +++ b/start.sh @@ -1,3 +1,3 @@ -source env/bin/activate +source venv/bin/activate source .env python3 src/manage.py runserver 0.0.0.0:8000 \ No newline at end of file From 06c89bb2b0891d6cc91c6ae1519ec420ed8e45f4 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Sun, 26 Feb 2023 16:57:10 -0500 Subject: [PATCH 108/305] Implement report model --- .gitignore | 1 + README.md | 5 ++++ reset_db.sh | 2 +- src/eatery_blue_backend/settings.py | 2 +- src/eatery_blue_backend/urls.py | 3 ++- src/{reports => report}/__init__.py | 0 src/report/admin.py | 4 ++++ src/report/apps.py | 6 +++++ src/report/models.py | 11 +++++++++ src/report/serializers.py | 8 +++++++ src/report/urls.py | 10 ++++++++ src/report/views.py | 7 ++++++ src/reports/admin.py | 4 ---- src/reports/apps.py | 4 ---- src/reports/controllers/create_report.py | 21 ----------------- src/reports/models.py | 9 -------- src/reports/urls.py | 10 -------- src/reports/views.py | 29 ------------------------ start.sh | 2 +- 19 files changed, 57 insertions(+), 81 deletions(-) rename src/{reports => report}/__init__.py (100%) create mode 100644 src/report/admin.py create mode 100644 src/report/apps.py create mode 100644 src/report/models.py create mode 100644 src/report/serializers.py create mode 100644 src/report/urls.py create mode 100644 src/report/views.py delete mode 100644 src/reports/admin.py delete mode 100644 src/reports/apps.py delete mode 100644 src/reports/controllers/create_report.py delete mode 100644 src/reports/models.py delete mode 100644 src/reports/urls.py delete mode 100644 src/reports/views.py diff --git a/.gitignore b/.gitignore index 54927b1..45af475 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ __pycache__/ .DS_Store .vscode/ env/ +venv/ .Python build/ develop-eggs/ diff --git a/README.md b/README.md index a2e0df4..9fd3dda 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ This is the backend for eatery-blue-backend. +SP23 Members +--------------- +- Mateo Weiner +- Kidus Zegeye + FA22 Members --------------- - Marya Kim diff --git a/reset_db.sh b/reset_db.sh index 3490cf3..055ec62 100644 --- a/reset_db.sh +++ b/reset_db.sh @@ -1,4 +1,4 @@ -source env/bin/activate +source venv/bin/activate source .env python3 src/manage.py makemigrations python3 src/manage.py migrate \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index da8722c..f7ae8c4 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -41,7 +41,7 @@ "eatery", "alert", "event", - "reports", + "report", "menu", "item", "category", diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 5b1abe9..ed64d64 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -20,7 +20,8 @@ path("admin/", admin.site.urls), path("eatery/", include("eatery.urls")), path("event/", include("event.urls")), - path("cdn/", include("cdn_parser.urls")) + path("cdn/", include("cdn_parser.urls")), + path("report/", include("report.urls")), #path("alert/", include("alert.urls")) #path("wait_time/", include("wait_time.urls")) ] diff --git a/src/reports/__init__.py b/src/report/__init__.py similarity index 100% rename from src/reports/__init__.py rename to src/report/__init__.py diff --git a/src/report/admin.py b/src/report/admin.py new file mode 100644 index 0000000..3742a74 --- /dev/null +++ b/src/report/admin.py @@ -0,0 +1,4 @@ +from django.contrib import admin +from report.models import Report + +admin.site.register(Report) diff --git a/src/report/apps.py b/src/report/apps.py new file mode 100644 index 0000000..a3e9973 --- /dev/null +++ b/src/report/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ReportConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = "report" \ No newline at end of file diff --git a/src/report/models.py b/src/report/models.py new file mode 100644 index 0000000..786d0ef --- /dev/null +++ b/src/report/models.py @@ -0,0 +1,11 @@ +from django.db import models +from eatery.models import Eatery + + +class Report(models.Model): + eatery = models.ForeignKey(Eatery, on_delete=models.CASCADE, null=True, blank=True) + content = models.TextField() + created = models.DateTimeField(auto_now_add=True) + +def __str__(self): + return self.content \ No newline at end of file diff --git a/src/report/serializers.py b/src/report/serializers.py new file mode 100644 index 0000000..0206d8e --- /dev/null +++ b/src/report/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers + +from report.models import Report + +class ReportSerializer(serializers.ModelSerializer): + class Meta: + model = Report + fields = ['id', 'eatery', 'content', 'created'] diff --git a/src/report/urls.py b/src/report/urls.py new file mode 100644 index 0000000..a50ae02 --- /dev/null +++ b/src/report/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from report import views + +router = DefaultRouter() +router.register(r'report', views.ReportViewSet) + +urlpatterns = [ + path('', include(router.urls)) +] \ No newline at end of file diff --git a/src/report/views.py b/src/report/views.py new file mode 100644 index 0000000..943fe71 --- /dev/null +++ b/src/report/views.py @@ -0,0 +1,7 @@ +from rest_framework import viewsets +from report.models import Report +from report.serializers import ReportSerializer + +class ReportViewSet(viewsets.ModelViewSet): + queryset = Report.objects.all() + serializer_class = ReportSerializer \ No newline at end of file diff --git a/src/reports/admin.py b/src/reports/admin.py deleted file mode 100644 index d590982..0000000 --- a/src/reports/admin.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.contrib import admin -from reports.models import ReportStore - -admin.site.register(ReportStore) diff --git a/src/reports/apps.py b/src/reports/apps.py deleted file mode 100644 index 5c787a3..0000000 --- a/src/reports/apps.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.apps import AppConfig - -class ReportConfig(AppConfig): - name = "reports" \ No newline at end of file diff --git a/src/reports/controllers/create_report.py b/src/reports/controllers/create_report.py deleted file mode 100644 index b78dabf..0000000 --- a/src/reports/controllers/create_report.py +++ /dev/null @@ -1,21 +0,0 @@ -from datetime import datetime -from typing import Optional - -from api.models import ReportStore -from eatery.datatype.Eatery import EateryID - - -class CreateReportController: - def __init__(self, type: str, content: str, eatery_id: Optional[EateryID] = None): - self.eatery_id = eatery_id - self.type = type - self.content = content - - def process(self): - current_timestamp = datetime.now().timestamp() - ReportStore.objects.create( - eatery_id=self.eatery_id.value if self.eatery_id else None, - type=self.type, - content=self.content, - created_timestamp=current_timestamp, - ) diff --git a/src/reports/models.py b/src/reports/models.py deleted file mode 100644 index 070a17e..0000000 --- a/src/reports/models.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.db import models -from eatery.models import Eatery - - -class ReportStore(models.Model): - eatery = models.ForeignKey(Eatery, on_delete=models.DO_NOTHING, null=True) - type = models.CharField(max_length=200) - content = models.TextField() - created_timestamp = models.IntegerField() diff --git a/src/reports/urls.py b/src/reports/urls.py deleted file mode 100644 index 652cad1..0000000 --- a/src/reports/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path -from views import ReportView - -urlpatterns = [ - path("", ReportView.as_view(), name="main"), - """ - path("delete", ReportView.as_view(),name="delete"), - path("post", ReportView.as_view(), name="post") - """ -] \ No newline at end of file diff --git a/src/reports/views.py b/src/reports/views.py deleted file mode 100644 index 8b74c75..0000000 --- a/src/reports/views.py +++ /dev/null @@ -1,29 +0,0 @@ -import json -from django.http import JsonResponse -from eatery.datatype.Eatery import EateryID -from rest_framework.views import APIView - -from reports.controllers.create_report import CreateReportController -from api.util.json import FieldType, error_json, success_json, verify_json_fields - -class ReportView(APIView): - def post(self, request): - json_body = json.loads(request.body) - if not verify_json_fields( - json_body, - { - "eatery_id": FieldType.INT or None, - "type": FieldType.STRING, - "content": FieldType.STRING, - }, - ["eatery_id"], - ): - return JsonResponse(error_json("Malformed Request")) - - id_provided = json_body.get("eatery_id") - CreateReportController( - type=json_body["type"], - content=json_body["content"], - eatery_id=EateryID(id_provided) if id_provided else None, - ).process() - return JsonResponse(success_json("Reported")) diff --git a/start.sh b/start.sh index 8994c63..bdcdbf5 100644 --- a/start.sh +++ b/start.sh @@ -1,3 +1,3 @@ -source env/bin/activate +source venv/bin/activate source .env python3 src/manage.py runserver 0.0.0.0:8000 \ No newline at end of file From b35dfc1300ae836ed3f9851b4617eef0380b9be6 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 1 Mar 2023 13:18:22 -0500 Subject: [PATCH 109/305] change env to venv --- reset_db.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reset_db.sh b/reset_db.sh index 3490cf3..055ec62 100644 --- a/reset_db.sh +++ b/reset_db.sh @@ -1,4 +1,4 @@ -source env/bin/activate +source venv/bin/activate source .env python3 src/manage.py makemigrations python3 src/manage.py migrate \ No newline at end of file From 8f347d3235117d4967ae56ee54cb840f34eafd3c Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 1 Mar 2023 16:41:18 -0500 Subject: [PATCH 110/305] Refactor categories to be menus --- src/category/controllers/populate_category.py | 77 ++++++++++--------- ...004_remove_category_menu_category_event.py | 25 ++++++ src/category/models.py | 5 +- src/category/serializers.py | 8 +- src/event/serializers.py | 19 +++-- 5 files changed, 82 insertions(+), 52 deletions(-) create mode 100644 src/category/migrations/0004_remove_category_menu_category_event.py diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index daad41e..f520b16 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -1,6 +1,6 @@ from category.models import Category from category.serializers import CategorySerializer -from item.models import Item +from item.models import Item """ Add categories to Category Model from CornellDiningNow. @@ -8,29 +8,27 @@ CornellDiningNow models cafe and dining hall categories differently, so they are parsed differently. """ -class PopulateCategoryController(): + +class PopulateCategoryController: def __init__(self): - self = self + self = self def generate_dining_hall_categories(self, json_event, menu): """ category_items = {"category_name" : id, ... } """ category_items = {} - for json_menu in json_event['menu']: - data = { - "menu" : int(menu.data['id']), - "category" : json_menu['category'] - } + for json_menu in json_event["menu"]: + data = {"event": int(menu.data["id"]), "category": json_menu["category"]} category = CategorySerializer(data=data) if category.is_valid(): category.save() else: print(category.errors) - - category_name = category.data['category'] - category_id = category.data['id'] + + category_name = category.data["category"] + category_id = category.data["id"] category_items[category_name] = category_id return category_items @@ -46,28 +44,25 @@ def generate_cafe_categories(self, json_eatery, menu): for item in dining_items: if item["category"] not in categories: categories.append(item["category"]) - data = { - "menu" : int(menu.data['id']), - "category" : item["category"] - } + data = {"event": int(menu.data["id"]), "category": item["category"]} category = CategorySerializer(data=data) if category.is_valid(): category.save() else: print(category.errors) - category_name = category.data['category'] - category_id = category.data['id'] + category_name = category.data["category"] + category_id = category.data["id"] category_items[category_name] = category_id - + return category_items - + def process(self, menus_dict, json_eateries): - """categories_dict = { eatery_id : - { menu[i] : {"category_name" : id, "category_name" : id}, - menu[i] : {"category_name" : id} - } - }""" + """categories_dict = { eatery_id : + { event[i] : {"category_name" : id, "category_name" : id}, + event[i] : {"category_name" : id} + } + }""" categories_dict = {} @@ -76,31 +71,37 @@ def process(self, menus_dict, json_eateries): categories_dict[eatery_id] = {} if eatery_id in menus_dict: - eatery_menus = menus_dict[eatery_id]; i=0 + eatery_menus = menus_dict[eatery_id] + i = 0 else: continue is_cafe = "Cafe" in { eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] - } + } """ For every event in an eatery --> for every menu in an eatery --> get categories """ json_dates = json_eatery["operatingHours"] - for json_date in json_dates: + for json_date in json_dates: json_events = json_date["events"] - for json_event in json_events: - if i < len(eatery_menus): - menu = eatery_menus[i]; i += 1 + for json_event in json_events: + if i < len(eatery_menus): + menu = eatery_menus[i] + i += 1 + + categories_dict[eatery_id][menu.data["id"]] = {} - categories_dict[eatery_id][menu.data['id']] = {} + if is_cafe: + categories = self.generate_cafe_categories( + json_eatery, menu + ) + else: + categories = self.generate_dining_hall_categories( + json_event, menu + ) - if is_cafe: - categories = self.generate_cafe_categories(json_eatery, menu) - else: - categories = self.generate_dining_hall_categories(json_event, menu) - - categories_dict[eatery_id][menu.data['id']] = categories + categories_dict[eatery_id][menu.data["id"]] = categories - return categories_dict \ No newline at end of file + return categories_dict diff --git a/src/category/migrations/0004_remove_category_menu_category_event.py b/src/category/migrations/0004_remove_category_menu_category_event.py new file mode 100644 index 0000000..8d432c2 --- /dev/null +++ b/src/category/migrations/0004_remove_category_menu_category_event.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0 on 2023-03-01 20:24 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0002_alter_event_end_alter_event_start'), + ('category', '0003_alter_category_menu'), + ] + + operations = [ + migrations.RemoveField( + model_name='category', + name='menu', + ), + migrations.AddField( + model_name='category', + name='event', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, related_name='categories', to='event.event'), + preserve_default=False, + ), + ] diff --git a/src/category/models.py b/src/category/models.py index dadc6c4..715a96e 100644 --- a/src/category/models.py +++ b/src/category/models.py @@ -1,8 +1,9 @@ from django.db import models from eatery.models import Eatery -from menu.models import Menu +from event.models import Event + class Category(models.Model): id = models.AutoField(primary_key=True) - menu = models.ForeignKey(Menu, related_name = 'categories', on_delete=models.DO_NOTHING) + event = models.ForeignKey(Event, related_name="menu", on_delete=models.DO_NOTHING) category = models.CharField(max_length=40, default="General") diff --git a/src/category/serializers.py b/src/category/serializers.py index a685969..1043bb5 100644 --- a/src/category/serializers.py +++ b/src/category/serializers.py @@ -4,14 +4,14 @@ class CategorySerializer(serializers.ModelSerializer): - id = serializers.IntegerField(read_only = True) - category = serializers.CharField(allow_null = True) + id = serializers.IntegerField(read_only=True) + category = serializers.CharField(allow_null=True) items = ItemSerializer(many=True, read_only=True) def create(self, validated_data): category, _ = Category.objects.get_or_create(**validated_data) return category - class Meta: + class Meta: model = Category - fields = ['id', 'category', 'menu', 'items'] \ No newline at end of file + fields = ["id", "category", "event", "items"] diff --git a/src/event/serializers.py b/src/event/serializers.py index eb7d981..9702ec9 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -1,19 +1,22 @@ from rest_framework import serializers from event.models import Event -from menu.serializers import MenuSerializer +from category.serializers import CategorySerializer + class EventSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False, read_only = True) - event_description = serializers.CharField(allow_null=True, allow_blank = True, default=None) + id = serializers.IntegerField(required=False, read_only=True) + event_description = serializers.CharField( + allow_null=True, allow_blank=True, default=None + ) start = serializers.DateTimeField() end = serializers.DateTimeField() - menus = MenuSerializer(many=True, read_only=True) - + menu = CategorySerializer(many=True, read_only=True) + def create(self, validated_data): event, _ = Event.objects.get_or_create(**validated_data) return event - class Meta: - model = Event - fields = ['id', 'eatery', 'event_description', 'start', 'end', 'menus'] \ No newline at end of file + class Meta: + model = Event + fields = ["id", "eatery", "event_description", "start", "end", "menu"] From 3af56060ab06061da5ae4aef4839192a71626d96 Mon Sep 17 00:00:00 2001 From: Shungo Najima Date: Wed, 1 Mar 2023 18:02:58 -0500 Subject: [PATCH 111/305] Delete CODEOWNERS --- .github/CODEOWNERS | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 947ec15..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @Archit404Error @connorreinhold @chalo2000 @markim21 @alanna-zhou \ No newline at end of file From c126e9ba7aa6b7ba34c0d8d8e84485e66e6b0545 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 1 Mar 2023 18:29:03 -0500 Subject: [PATCH 112/305] Implement alerts --- src/alert/apps.py | 4 +++- src/alert/models.py | 4 ++++ src/alert/serializers.py | 25 +---------------------- src/alert/urls.py | 12 +++++------ src/alert/views.py | 35 ++++++--------------------------- src/eatery_blue_backend/urls.py | 2 +- src/report/urls.py | 2 +- 7 files changed, 22 insertions(+), 62 deletions(-) diff --git a/src/alert/apps.py b/src/alert/apps.py index ddb6053..7857426 100644 --- a/src/alert/apps.py +++ b/src/alert/apps.py @@ -1,4 +1,6 @@ -from django.apps import AppConfig +from django.apps import AppConfig + class AlertConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' name = "alert" \ No newline at end of file diff --git a/src/alert/models.py b/src/alert/models.py index f6df815..4639bb8 100644 --- a/src/alert/models.py +++ b/src/alert/models.py @@ -8,3 +8,7 @@ class Alert(models.Model): description = models.CharField(max_length = 250) start_timestamp = models.IntegerField() end_timestamp = models.IntegerField() + + +def __str__(self): + return self.description \ No newline at end of file diff --git a/src/alert/serializers.py b/src/alert/serializers.py index 8a178d1..657bdc1 100644 --- a/src/alert/serializers.py +++ b/src/alert/serializers.py @@ -1,31 +1,8 @@ -from wsgiref import validate from rest_framework import serializers -from models import Alert +from alert.models import Alert class AlertSerializer(serializers.ModelSerializer): - class Meta: - model = Alert - fields = "__all__" - - def create(self, validated_data): - """ - Create and return a new Alert. - """ - return Alert.objects.create(**validated_data) - - def update(self, instance, validated_data): - """ - Update an existing Alert. - """ - instance.description = validated_data.get('description', instance.description) - instance.start_timestamp = validated_data.get('start_timestamp',instance.start_timestamp) - instance.end_timestamp = validated_data.get('end_timestamp', instance.end_timestamp) - - instance.save() - return instance - -class AlertSubSerializer(serializers.ModelSerializer): class Meta: model = Alert fields = ['id', 'description', 'start_timestamp', 'end_timestamp'] diff --git a/src/alert/urls.py b/src/alert/urls.py index 1723ae8..d1b575c 100644 --- a/src/alert/urls.py +++ b/src/alert/urls.py @@ -1,10 +1,10 @@ -from django.urls import path +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from alert import views -from alert.views import Alerts +router = DefaultRouter() +router.register(r'', views.AlertViewSet) urlpatterns = [ - path("", Alerts.as_view(), name="get_all_alerts"), - path("/", Alerts.as_view(), name="get_eatery_alerts"), - path("/edit/", Alerts.as_view(), name="edit_eatery_alerts"), - path("/edit//", Alerts.as_view(), name="delete_eatery_alert") + path('', include(router.urls)) ] \ No newline at end of file diff --git a/src/alert/views.py b/src/alert/views.py index 292817b..f797211 100644 --- a/src/alert/views.py +++ b/src/alert/views.py @@ -1,30 +1,7 @@ -from serializers import AlertSerializer -from models import Alert -from django.http import JsonResponse -from api.util.json import FieldType, error_json, success_json, verify_json_fields -from rest_framework.views import APIView -from rest_framework import viewsets +from rest_framework import viewsets +from alert.models import Alert +from alert.serializers import AlertSerializer -""" -Basic CRUD functions: -- create an alert for eatery -- get alert for an eatery -- update an alert for an eatery -- delete alert for an eatery -""" -class AlertView(APIView): - model = Alert - - def get(self, request): - queryset = Alert.objects.all() - serializer = AlertSerializer(queryset, many=True) - return JsonResponse(serializer.data) - - def post(self, request): - pass - - def patch(self, request): - pass - - def delete(self, request): - pass \ No newline at end of file +class AlertViewSet(viewsets.ModelViewSet): + queryset = Alert.objects.all() + serializer_class = AlertSerializer \ No newline at end of file diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index ed64d64..4621df2 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -22,6 +22,6 @@ path("event/", include("event.urls")), path("cdn/", include("cdn_parser.urls")), path("report/", include("report.urls")), - #path("alert/", include("alert.urls")) + path("alert/", include("alert.urls")) #path("wait_time/", include("wait_time.urls")) ] diff --git a/src/report/urls.py b/src/report/urls.py index a50ae02..2581e70 100644 --- a/src/report/urls.py +++ b/src/report/urls.py @@ -3,7 +3,7 @@ from report import views router = DefaultRouter() -router.register(r'report', views.ReportViewSet) +router.register(r'', views.ReportViewSet) urlpatterns = [ path('', include(router.urls)) From 4a3caf5363c55de8c6965a9e27b6294d8ad56041 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Thu, 2 Mar 2023 13:28:30 -0500 Subject: [PATCH 113/305] remove unnecessary code --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 45af475..65745e8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ __pycache__/ # More Python stuff .DS_Store .vscode/ -env/ venv/ .Python build/ From 5c59137c37065b29b82861121c745156d581c51e Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 8 Mar 2023 18:19:39 -0500 Subject: [PATCH 114/305] Implement user model --- src/eatery_blue_backend/settings.py | 2 ++ src/eatery_blue_backend/urls.py | 3 ++- src/user/__init__.py | 0 src/user/admin.py | 5 +++++ src/user/apps.py | 6 ++++++ src/user/migrations/__init__.py | 0 src/user/models.py | 9 +++++++++ src/user/serializers.py | 9 +++++++++ src/user/tests.py | 3 +++ src/user/views.py | 7 +++++++ 10 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/user/__init__.py create mode 100644 src/user/admin.py create mode 100644 src/user/apps.py create mode 100644 src/user/migrations/__init__.py create mode 100644 src/user/models.py create mode 100644 src/user/serializers.py create mode 100644 src/user/tests.py create mode 100644 src/user/views.py diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index f7ae8c4..8433a84 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -123,6 +123,8 @@ }, ] +AUTH_USER_MODEL = "user.User" + # Internationalization # https://docs.djangoproject.com/en/4.0/topics/i18n/ diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 4621df2..f5fb749 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -22,6 +22,7 @@ path("event/", include("event.urls")), path("cdn/", include("cdn_parser.urls")), path("report/", include("report.urls")), - path("alert/", include("alert.urls")) + path("alert/", include("alert.urls")), + path("user/", include("user.urls")) #path("wait_time/", include("wait_time.urls")) ] diff --git a/src/user/__init__.py b/src/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user/admin.py b/src/user/admin.py new file mode 100644 index 0000000..888bc72 --- /dev/null +++ b/src/user/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import User + +admin.site.register(User, UserAdmin) \ No newline at end of file diff --git a/src/user/apps.py b/src/user/apps.py new file mode 100644 index 0000000..36cce4c --- /dev/null +++ b/src/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user' diff --git a/src/user/migrations/__init__.py b/src/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user/models.py b/src/user/models.py new file mode 100644 index 0000000..1fc1b24 --- /dev/null +++ b/src/user/models.py @@ -0,0 +1,9 @@ +from django.contrib.auth.models import AbstractUser +from django.db import models + +class User(AbstractUser): + netid = models.TextField(unique=True) + favorite_items = models.ManyToManyField('item.Item', related_name='users', blank=True) + +def __str__(self): + return self.netid \ No newline at end of file diff --git a/src/user/serializers.py b/src/user/serializers.py new file mode 100644 index 0000000..429639d --- /dev/null +++ b/src/user/serializers.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +from user.models import User + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['url', 'id', 'username', 'first_name', 'last_name', 'email', + 'is_staff', 'is_active', 'date_joined', 'netid', 'favorite_items'] diff --git a/src/user/tests.py b/src/user/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/user/views.py b/src/user/views.py new file mode 100644 index 0000000..499368e --- /dev/null +++ b/src/user/views.py @@ -0,0 +1,7 @@ +from rest_framework import viewsets +from user.models import User +from user.serializers import UserSerializer + +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer \ No newline at end of file From 57d0a968b7a8c8b9446f7bdc475a6e62a07efb8d Mon Sep 17 00:00:00 2001 From: Shungo Najima Date: Sat, 11 Mar 2023 18:58:47 -0500 Subject: [PATCH 115/305] Allow manual GitHub actions run --- .github/workflows/deploy-dev.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 67c5193..89dc66a 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -3,6 +3,7 @@ name: Docker Build & Push and Deploy to eatery-blue-backend on: push: branches: [master] + workflow_dispatch: jobs: path-context: From 45f1d0192a05e7bbb05ca0f1c2318d22bf6328b6 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Mon, 13 Mar 2023 23:58:48 -0400 Subject: [PATCH 116/305] Migrate from sqlite local db to postgres + add db snapshots --- reset_db.sh | 2 +- .../migrations/0005_alter_category_event.py | 20 +++++++++++++ ...tery_location_alter_eatery_menu_summary.py | 23 +++++++++++++++ src/eatery/models.py | 4 +-- src/eatery_blue_backend/settings.py | 29 ++++++------------- src/eatery_db_snapshots/alert_store.txt | 1 + .../category_item_association.txt | 19 ++++++++++++ src/eatery_db_snapshots/category_store.txt | 7 +++++ .../day_of_week_event_schedule.txt | 2 ++ src/eatery_db_snapshots/item_store.txt | 8 +++++ src/eatery_db_snapshots/menu_store.txt | 3 ++ src/eatery_db_snapshots/subitem_store.txt | 14 +++++++++ .../0003_alter_event_event_description.py | 18 ++++++++++++ .../0004_alter_event_event_description.py | 18 ++++++++++++ src/event/models.py | 4 +-- start.sh | 2 +- 16 files changed, 148 insertions(+), 26 deletions(-) create mode 100644 src/category/migrations/0005_alter_category_event.py create mode 100644 src/eatery/migrations/0003_alter_eatery_location_alter_eatery_menu_summary.py create mode 100644 src/eatery_db_snapshots/alert_store.txt create mode 100644 src/eatery_db_snapshots/category_item_association.txt create mode 100644 src/eatery_db_snapshots/category_store.txt create mode 100644 src/eatery_db_snapshots/day_of_week_event_schedule.txt create mode 100644 src/eatery_db_snapshots/item_store.txt create mode 100644 src/eatery_db_snapshots/menu_store.txt create mode 100644 src/eatery_db_snapshots/subitem_store.txt create mode 100644 src/event/migrations/0003_alter_event_event_description.py create mode 100644 src/event/migrations/0004_alter_event_event_description.py diff --git a/reset_db.sh b/reset_db.sh index 055ec62..609211d 100644 --- a/reset_db.sh +++ b/reset_db.sh @@ -1,4 +1,4 @@ source venv/bin/activate -source .env +source .envrc python3 src/manage.py makemigrations python3 src/manage.py migrate \ No newline at end of file diff --git a/src/category/migrations/0005_alter_category_event.py b/src/category/migrations/0005_alter_category_event.py new file mode 100644 index 0000000..d11a4da --- /dev/null +++ b/src/category/migrations/0005_alter_category_event.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0 on 2023-03-08 22:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0002_alter_event_end_alter_event_start'), + ('category', '0004_remove_category_menu_category_event'), + ] + + operations = [ + migrations.AlterField( + model_name='category', + name='event', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='menu', to='event.event'), + ), + ] diff --git a/src/eatery/migrations/0003_alter_eatery_location_alter_eatery_menu_summary.py b/src/eatery/migrations/0003_alter_eatery_location_alter_eatery_menu_summary.py new file mode 100644 index 0000000..c1762cd --- /dev/null +++ b/src/eatery/migrations/0003_alter_eatery_location_alter_eatery_menu_summary.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0 on 2023-03-13 17:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0002_alter_eatery_menu_summary'), + ] + + operations = [ + migrations.AlterField( + model_name='eatery', + name='location', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='eatery', + name='menu_summary', + field=models.TextField(blank=True, default='', null=True), + ), + ] diff --git a/src/eatery/models.py b/src/eatery/models.py index 35a0bde..d23b86f 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -11,9 +11,9 @@ class CampusArea(models.TextChoices): id = models.IntegerField(primary_key=True) name = models.CharField(max_length=40) - menu_summary = models.CharField(max_length=60, blank=True, null=True, default="") + menu_summary = models.TextField(blank=True, null=True, default="") image_url = models.URLField(blank=True) - location = models.CharField(max_length=30, blank=True) + location = models.TextField(blank=True) campus_area = models.CharField( max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True ) diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index f7ae8c4..b27b138 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -83,27 +83,16 @@ # Database # https://docs.djangoproject.com/en/4.0/ref/settings/#databases -IS_PROD = os.getenv("IS_PROD") == "True" - -if IS_PROD: - DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql_psycopg2", - "NAME": os.getenv("POSTGRES_NAME"), - "USER": os.getenv("POSTGRES_USER"), - "PASSWORD": os.getenv("POSTGRES_PASSWORD"), - "HOST": os.getenv("POSTGRES_HOST"), - "PORT": os.getenv("POSTGRES_PORT"), - } +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql_psycopg2", + "NAME": os.getenv("POSTGRES_NAME"), + "USER": os.getenv("POSTGRES_USER"), + "PASSWORD": os.getenv("POSTGRES_PASSWORD"), + "HOST": os.getenv("POSTGRES_HOST"), + "PORT": os.getenv("POSTGRES_PORT"), } -else: - DATABASES = { - "default": { - "ENGINE": "django.db.backends.sqlite3", - "NAME": str(os.path.join(BASE_DIR, "db.sqlite3")), - } - } - +} # Password validation # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators diff --git a/src/eatery_db_snapshots/alert_store.txt b/src/eatery_db_snapshots/alert_store.txt new file mode 100644 index 0000000..111ed41 --- /dev/null +++ b/src/eatery_db_snapshots/alert_store.txt @@ -0,0 +1 @@ +{"id": 1, "description": "No BRBs after 3:00pm", "start_timestamp": 1641833325, "end_timestamp": 2641833325, "eatery": 33} diff --git a/src/eatery_db_snapshots/category_item_association.txt b/src/eatery_db_snapshots/category_item_association.txt new file mode 100644 index 0000000..3a583e8 --- /dev/null +++ b/src/eatery_db_snapshots/category_item_association.txt @@ -0,0 +1,19 @@ +{"id": 1, "item": 2, "category": 1} +{"id": 2, "item": 7, "category": 1} +{"id": 3, "item": 8, "category": 1} +{"id": 4, "item": 1, "category": 2} +{"id": 5, "item": 2, "category": 3} +{"id": 6, "item": 7, "category": 3} +{"id": 7, "item": 8, "category": 3} +{"id": 8, "item": 1, "category": 4} +{"id": 9, "item": 3, "category": 5} +{"id": 10, "item": 4, "category": 5} +{"id": 11, "item": 5, "category": 5} +{"id": 12, "item": 6, "category": 5} +{"id": 13, "item": 2, "category": 6} +{"id": 14, "item": 7, "category": 6} +{"id": 15, "item": 8, "category": 6} +{"id": 16, "item": 3, "category": 7} +{"id": 17, "item": 4, "category": 7} +{"id": 18, "item": 5, "category": 7} +{"id": 19, "item": 6, "category": 7} diff --git a/src/eatery_db_snapshots/category_store.txt b/src/eatery_db_snapshots/category_store.txt new file mode 100644 index 0000000..c02b697 --- /dev/null +++ b/src/eatery_db_snapshots/category_store.txt @@ -0,0 +1,7 @@ +{"id": 1, "category": "Smoothies", "menu": 1} +{"id": 2, "category": "Enchiladas", "menu": 1} +{"id": 3, "category": "Smoothies", "menu": 2} +{"id": 4, "category": "Enchiladas", "menu": 2} +{"id": 5, "category": "Desserts", "menu": 2} +{"id": 6, "category": "Smoothies", "menu": 3} +{"id": 7, "category": "Desserts", "menu": 3} diff --git a/src/eatery_db_snapshots/day_of_week_event_schedule.txt b/src/eatery_db_snapshots/day_of_week_event_schedule.txt new file mode 100644 index 0000000..08ed488 --- /dev/null +++ b/src/eatery_db_snapshots/day_of_week_event_schedule.txt @@ -0,0 +1,2 @@ +{"id": 1, "event_description": "Breakfast", "day_of_week": "Monday", "start": "12:00:00", "end": "04:00:00", "eatery": 33, "menu": 1} +{"id": 2, "event_description": "General", "day_of_week": "Monday", "start": "16:00:00", "end": "03:00:00", "eatery": 33, "menu": 2} diff --git a/src/eatery_db_snapshots/item_store.txt b/src/eatery_db_snapshots/item_store.txt new file mode 100644 index 0000000..c415384 --- /dev/null +++ b/src/eatery_db_snapshots/item_store.txt @@ -0,0 +1,8 @@ +{"id": 1, "name": "Enchilada", "description": "A tortilla-wrapped enchilada with your choice of toppings", "base_price": 6.35, "eatery": 33} +{"id": 2, "name": "Veggie Goddess", "description": "Spinach, cucumber, cornflakes, carrots", "base_price": 5.0, "eatery": 33} +{"id": 3, "name": "Cookie", "description": "", "base_price": 1.8, "eatery": 33} +{"id": 4, "name": "Brownie", "description": "", "base_price": 1.8, "eatery": 33} +{"id": 5, "name": "Candy Bar", "description": "", "base_price": 1.8, "eatery": 33} +{"id": 6, "name": "Vegan Cookie", "description": "", "base_price": 3.65, "eatery": 33} +{"id": 7, "name": "Hawaiian Sunrise", "description": "Orange, mango, strawberries, coconut", "base_price": 5.0, "eatery": 33} +{"id": 8, "name": "Frightful Frenzy", "description": "Chocolate, oreo, peanut butter, marshmellow", "base_price": 5.0, "eatery": 33} diff --git a/src/eatery_db_snapshots/menu_store.txt b/src/eatery_db_snapshots/menu_store.txt new file mode 100644 index 0000000..4b015cf --- /dev/null +++ b/src/eatery_db_snapshots/menu_store.txt @@ -0,0 +1,3 @@ +{"id": 1, "name": "Mac's Weekday Morning Lunch Menu", "eatery": 33} +{"id": 2, "name": "Mac's Weekday Afternoon Menu", "eatery": 33} +{"id": 3, "name": "Mac's Saturday Menu", "eatery": 33} diff --git a/src/eatery_db_snapshots/subitem_store.txt b/src/eatery_db_snapshots/subitem_store.txt new file mode 100644 index 0000000..4cce5be --- /dev/null +++ b/src/eatery_db_snapshots/subitem_store.txt @@ -0,0 +1,14 @@ +{"id": 1, "additional_price": 1.0, "total_price": null, "name": "Chicken", "item_subsection": "Protein", "item": 1} +{"id": 2, "additional_price": 2.0, "total_price": null, "name": "Steak", "item_subsection": "Protein", "item": 1} +{"id": 3, "additional_price": 0.0, "total_price": null, "name": "Beans", "item_subsection": "Protein", "item": 1} +{"id": 4, "additional_price": 0.0, "total_price": null, "name": "Lettuce", "item_subsection": "Toppings", "item": 1} +{"id": 5, "additional_price": 0.0, "total_price": null, "name": "Cheese", "item_subsection": "Toppings", "item": 1} +{"id": 6, "additional_price": null, "total_price": 5.0, "name": "Small", "item_subsection": "Sizes", "item": 2} +{"id": 7, "additional_price": null, "total_price": 6.0, "name": "Medium", "item_subsection": "Sizes", "item": 2} +{"id": 8, "additional_price": null, "total_price": 7.0, "name": "Large", "item_subsection": "Sizes", "item": 2} +{"id": 9, "additional_price": null, "total_price": 5.0, "name": "Small", "item_subsection": "Sizes", "item": 7} +{"id": 10, "additional_price": null, "total_price": 6.0, "name": "Medium", "item_subsection": "Sizes", "item": 7} +{"id": 11, "additional_price": null, "total_price": 7.0, "name": "Large", "item_subsection": "Sizes", "item": 8} +{"id": 12, "additional_price": null, "total_price": 5.0, "name": "Small", "item_subsection": "Sizes", "item": 8} +{"id": 13, "additional_price": null, "total_price": 6.0, "name": "Medium", "item_subsection": "Sizes", "item": 8} +{"id": 14, "additional_price": null, "total_price": 7.0, "name": "Large", "item_subsection": "Sizes", "item": 8} diff --git a/src/event/migrations/0003_alter_event_event_description.py b/src/event/migrations/0003_alter_event_event_description.py new file mode 100644 index 0000000..24db4b5 --- /dev/null +++ b/src/event/migrations/0003_alter_event_event_description.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2023-03-13 17:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0002_alter_event_end_alter_event_start'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='event_description', + field=models.CharField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', max_length=30, null=True), + ), + ] diff --git a/src/event/migrations/0004_alter_event_event_description.py b/src/event/migrations/0004_alter_event_event_description.py new file mode 100644 index 0000000..eca300c --- /dev/null +++ b/src/event/migrations/0004_alter_event_event_description.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2023-03-13 17:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0003_alter_event_event_description'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='event_description', + field=models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', null=True), + ), + ] diff --git a/src/event/models.py b/src/event/models.py index 921302a..68c5066 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -14,7 +14,7 @@ class EventDescription(models.TextChoices): class Event(models.Model): id = models.AutoField(primary_key=True) eatery = models.ForeignKey(Eatery, related_name = "events", on_delete=models.DO_NOTHING) - event_description = models.CharField( - choices=EventDescription.choices, max_length=10, default = EventDescription.GENERAL, blank=True, null = True) + event_description = models.TextField( + choices=EventDescription.choices, default = EventDescription.GENERAL, blank=True, null = True) start = models.DateTimeField() end = models.DateTimeField() diff --git a/start.sh b/start.sh index bdcdbf5..7ad04bf 100644 --- a/start.sh +++ b/start.sh @@ -1,3 +1,3 @@ source venv/bin/activate -source .env +source .envrc python3 src/manage.py runserver 0.0.0.0:8000 \ No newline at end of file From c2a908e17e46c2153033e6b239fe8de48ef1ae77 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Date: Tue, 14 Mar 2023 17:13:11 -0400 Subject: [PATCH 117/305] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 9fd3dda..87e7b9b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ This is the backend for eatery-blue-backend. +# Postgres Setup +--------------- +* Install PostgresSQL here at https://www.postgresql.org/download/ +* Login to postgres via command line by entering ``psql postgres`` +* Create the eatery database via ``create database eatery-dev;`` +* Quit psql via ``\q`` +* To set up the tables, make sure current working directory is the src folder and run ``bash reset_db.sh`` +* To set up eatery data, run ``bash start.sh`` and hit the ``/cdn/populate/`` endpoint to populate the tables + SP23 Members --------------- - Mateo Weiner From 82c6e3517ca3460d2d44738c316c4433abda7da4 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Date: Wed, 15 Mar 2023 14:37:55 -0400 Subject: [PATCH 118/305] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 87e7b9b..ae09572 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This is the backend for eatery-blue-backend. * Login to postgres via command line by entering ``psql postgres`` * Create the eatery database via ``create database eatery-dev;`` * Quit psql via ``\q`` +* Create an .envrc file and fill out the environment variables from the .envrctemplate file corresponding to your local postgres database * To set up the tables, make sure current working directory is the src folder and run ``bash reset_db.sh`` * To set up eatery data, run ``bash start.sh`` and hit the ``/cdn/populate/`` endpoint to populate the tables From de56cdd7b0c589503e3ca5065cf62fc0e682db0d Mon Sep 17 00:00:00 2001 From: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Date: Wed, 15 Mar 2023 14:39:36 -0400 Subject: [PATCH 119/305] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae09572..d4c3989 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is the backend for eatery-blue-backend. --------------- * Install PostgresSQL here at https://www.postgresql.org/download/ * Login to postgres via command line by entering ``psql postgres`` -* Create the eatery database via ``create database eatery-dev;`` +* Create the eatery database via ``create database "eatery-dev";`` * Quit psql via ``\q`` * Create an .envrc file and fill out the environment variables from the .envrctemplate file corresponding to your local postgres database * To set up the tables, make sure current working directory is the src folder and run ``bash reset_db.sh`` From 0300933db6014df68686357363d9efa49309e8dc Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 15 Mar 2023 14:59:57 -0400 Subject: [PATCH 120/305] Fix user model --- src/eatery_blue_backend/settings.py | 1 + src/eatery_blue_backend/urls.py | 2 +- src/user/migrations/0001_initial.py | 47 +++++++++++++++++++++++++++++ src/user/urls.py | 10 ++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/user/migrations/0001_initial.py create mode 100644 src/user/urls.py diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 45d7c94..97c9440 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -47,6 +47,7 @@ "category", "cdn_parser", "rest_framework", + "user", ] MIDDLEWARE = [ diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index f5fb749..26d5186 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -23,6 +23,6 @@ path("cdn/", include("cdn_parser.urls")), path("report/", include("report.urls")), path("alert/", include("alert.urls")), - path("user/", include("user.urls")) + path("user/", include("user.urls")), #path("wait_time/", include("wait_time.urls")) ] diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py new file mode 100644 index 0000000..6afe154 --- /dev/null +++ b/src/user/migrations/0001_initial.py @@ -0,0 +1,47 @@ +# Generated by Django 4.0 on 2023-03-15 18:41 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('item', '0002_alter_item_base_price'), + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('netid', models.TextField(unique=True)), + ('favorite_items', models.ManyToManyField(blank=True, related_name='users', to='item.Item')), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/src/user/urls.py b/src/user/urls.py new file mode 100644 index 0000000..a60cd47 --- /dev/null +++ b/src/user/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from user import views + +router = DefaultRouter() +router.register(r'', views.UserViewSet) + +urlpatterns = [ + path('', include(router.urls)) +] \ No newline at end of file From d8b337f03cd24f82b8f5fc965fe741055c214c21 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 15 Mar 2023 17:51:38 -0400 Subject: [PATCH 121/305] Change path to eatery_store.txt --- src/eatery/controllers/populate_eatery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index 20e53cb..21afe49 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -49,7 +49,7 @@ def add_eatery_store(self): """ Create eatery objects from an eatery json from a eatery_db_snapshot, and add to Eatery table. """ - folder_path = "src/eatery/util/" + folder_path = "./src/eatery/util/" file_name = SnapshotFileName.EATERY_STORE with open(f"{folder_path}{file_name.value}", "r") as file: From 7a3ac0ba0f802ef49e25d9631fd553c5503b9606 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 15 Mar 2023 18:35:00 -0400 Subject: [PATCH 122/305] Modify file structure for docker container compatibility --- docker-compose.yml | 4 ++-- reset_db.sh | 4 ---- src/eatery/controllers/populate_eatery.py | 3 ++- src/reset_db.sh | 4 ++++ src/start.sh | 3 +++ start.sh | 3 --- 6 files changed, 11 insertions(+), 10 deletions(-) delete mode 100644 reset_db.sh create mode 100644 src/reset_db.sh create mode 100644 src/start.sh delete mode 100644 start.sh diff --git a/docker-compose.yml b/docker-compose.yml index 57b1406..fd628fe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3' +version: "3" services: app: @@ -7,4 +7,4 @@ services: command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:80" ports: - "80:80" - restart: always \ No newline at end of file + restart: always diff --git a/reset_db.sh b/reset_db.sh deleted file mode 100644 index 609211d..0000000 --- a/reset_db.sh +++ /dev/null @@ -1,4 +0,0 @@ -source venv/bin/activate -source .envrc -python3 src/manage.py makemigrations -python3 src/manage.py migrate \ No newline at end of file diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index 21afe49..4c9f617 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -49,7 +49,8 @@ def add_eatery_store(self): """ Create eatery objects from an eatery json from a eatery_db_snapshot, and add to Eatery table. """ - folder_path = "./src/eatery/util/" + + folder_path = "./eatery/util/" file_name = SnapshotFileName.EATERY_STORE with open(f"{folder_path}{file_name.value}", "r") as file: diff --git a/src/reset_db.sh b/src/reset_db.sh new file mode 100644 index 0000000..f68ba6e --- /dev/null +++ b/src/reset_db.sh @@ -0,0 +1,4 @@ +source ../venv/bin/activate +source ../.envrc +python3 manage.py makemigrations +python3 manage.py migrate \ No newline at end of file diff --git a/src/start.sh b/src/start.sh new file mode 100644 index 0000000..d0b55a7 --- /dev/null +++ b/src/start.sh @@ -0,0 +1,3 @@ +source ../venv/bin/activate +source ../.envrc +python3 manage.py runserver 0.0.0.0:8000 \ No newline at end of file diff --git a/start.sh b/start.sh deleted file mode 100644 index 7ad04bf..0000000 --- a/start.sh +++ /dev/null @@ -1,3 +0,0 @@ -source venv/bin/activate -source .envrc -python3 src/manage.py runserver 0.0.0.0:8000 \ No newline at end of file From 55308577180396678f4f6cb21fcc0375121df89a Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Sun, 19 Mar 2023 17:02:35 -0400 Subject: [PATCH 123/305] Add scheduler.py and populate_models.py --- requirements.txt | 1 + src/cdn_parser/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/populate_models.py | 13 +++++++++++++ src/eatery/controllers/populate_eatery.py | 8 ++++---- src/scheduler.py | 19 +++++++++++++++++++ 6 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 src/cdn_parser/management/__init__.py create mode 100644 src/cdn_parser/management/commands/__init__.py create mode 100644 src/cdn_parser/management/commands/populate_models.py create mode 100644 src/scheduler.py diff --git a/requirements.txt b/requirements.txt index d86038e..fdca582 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,6 +27,7 @@ pytz==2021.3 requests==2.26.0 requests-oauthlib==1.3.0 rsa==4.8 +schedule==1.1.0 six==1.16.0 sqlparse==0.4.2 tomli==1.2.3 diff --git a/src/cdn_parser/management/__init__.py b/src/cdn_parser/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cdn_parser/management/commands/__init__.py b/src/cdn_parser/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/cdn_parser/management/commands/populate_models.py b/src/cdn_parser/management/commands/populate_models.py new file mode 100644 index 0000000..3042f74 --- /dev/null +++ b/src/cdn_parser/management/commands/populate_models.py @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand +from django.utils import timezone +from cdn_parser.controllers.populate_models import CornellDiningNowController +from datetime import datetime + +class Command(BaseCommand): + help = 'Populates all models' + def handle(self, *args, **kwargs): + self.stdout.write(f"Populating models at {datetime.now()} UTC") + start = int(datetime.now().timestamp()) + CornellDiningNowController().process() + self.stdout.write(f"Populated models ({int(datetime.now().timestamp()) - start}s)") + diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index 4c9f617..8e50100 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -42,8 +42,8 @@ def generate_eatery(self, json_eatery): eatery = EaterySerializer(data=data) if eatery.is_valid(): eatery.save() - else: - print(eatery.errors) + #else: + #print(eatery.errors) def add_eatery_store(self): """ @@ -75,8 +75,8 @@ def add_eatery_store(self): if serialized.is_valid(): serialized.save() - else: - print(serialized.errors) + #else: + #print(serialized.errors) def process(self, json_eateries): for json_eatery in json_eateries: diff --git a/src/scheduler.py b/src/scheduler.py new file mode 100644 index 0000000..8bf8139 --- /dev/null +++ b/src/scheduler.py @@ -0,0 +1,19 @@ +import os +import django +from django.core import management +import schedule +import time + +try: + os.environ["DJANGO_SETTINGS_MODULE"] +except: + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") + django.setup() + +print("Scheduler started") +schedule.every(2).minutes.do(management.call_command('populate_models') +) + +while True: + schedule.run_pending() + time.sleep(1) \ No newline at end of file From 2d9f94142a1d01cfc5801ef4a1014fce33e9a430 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Mon, 20 Mar 2023 00:38:03 -0400 Subject: [PATCH 124/305] Fix scheduler script and modify compose files --- docker-compose.server.yml | 8 +++++--- docker-compose.yml | 4 +++- src/cdn_parser/controllers/populate_models.py | 7 ++++++- src/scheduler.py | 7 +++++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docker-compose.server.yml b/docker-compose.server.yml index f7aa260..9ca2f07 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -1,10 +1,12 @@ -version: '3' +version: "3" services: app: image: cornellappdev/eatery-blue-backend:${IMAGE_TAG} env_file: .env - command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000" + stdin_open: true # docker run -i + tty: true # docker run -t + command: sh -c "python manage.py migrate && python manage.py populate_models && python manage.py runserver 0.0.0.0:8000 & python scheduler.py" ports: - "8000:8000" - restart: always \ No newline at end of file + restart: always diff --git a/docker-compose.yml b/docker-compose.yml index fd628fe..e75e73c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,9 @@ services: app: build: . env_file: .env - command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:80" + stdin_open: true # docker run -i + tty: true # docker run -t + command: sh -c "python manage.py migrate && { python scheduler.py & python manage.py runserver 0.0.0.0:80; }" ports: - "80:80" restart: always diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py index 307ffcc..0d15cde 100644 --- a/src/cdn_parser/controllers/populate_models.py +++ b/src/cdn_parser/controllers/populate_models.py @@ -49,16 +49,21 @@ def process(self): """ json_eateries = self.get_json() - + print("Populating eateries") PopulateEateryController().process(json_eateries) + print("Populating events") events_dict = PopulateEventController().process(json_eateries) + print("Populating menus") menus_dict = PopulateMenuController().process(events_dict, json_eateries) + print("Populating categories") categories_dict = PopulateCategoryController().process(menus_dict, json_eateries) + print("Populating items") PopulateItemController().process(categories_dict, json_eateries) + print("Done populating") diff --git a/src/scheduler.py b/src/scheduler.py index 8bf8139..79f2b08 100644 --- a/src/scheduler.py +++ b/src/scheduler.py @@ -11,9 +11,12 @@ django.setup() print("Scheduler started") -schedule.every(2).minutes.do(management.call_command('populate_models') -) +def populate_models(): + management.call_command('populate_models') +schedule.every(12).hours.do(populate_models) + +populate_models() while True: schedule.run_pending() time.sleep(1) \ No newline at end of file From 2bc8fb161dce7dd6e79b714cfe0df348aa4f12d5 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Mon, 20 Mar 2023 01:16:52 -0400 Subject: [PATCH 125/305] Schedule every day --- src/scheduler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scheduler.py b/src/scheduler.py index 79f2b08..7c029fc 100644 --- a/src/scheduler.py +++ b/src/scheduler.py @@ -14,7 +14,7 @@ def populate_models(): management.call_command('populate_models') -schedule.every(12).hours.do(populate_models) +schedule.every().day.do(populate_models) populate_models() while True: From 216940c00e60ed1f372d5b6937b5909072025374 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Mon, 20 Mar 2023 16:52:15 -0400 Subject: [PATCH 126/305] Fix docker-compose.server --- docker-compose.server.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.server.yml b/docker-compose.server.yml index 9ca2f07..04418ba 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -6,7 +6,7 @@ services: env_file: .env stdin_open: true # docker run -i tty: true # docker run -t - command: sh -c "python manage.py migrate && python manage.py populate_models && python manage.py runserver 0.0.0.0:8000 & python scheduler.py" + command: sh -c "python manage.py migrate && { python scheduler.py & python manage.py runserver 0.0.0.0:80; }" ports: - "8000:8000" restart: always From 05b1a890f3692f12dba5767d8664f79359fd1597 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Mon, 20 Mar 2023 19:49:17 -0400 Subject: [PATCH 127/305] Update README and reset_db.sh --- README.md | 34 ++++++++++++++++++---------------- src/reset_db.sh | 3 ++- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d4c3989..7389d9f 100644 --- a/README.md +++ b/README.md @@ -3,26 +3,28 @@ This is the backend for eatery-blue-backend. # Postgres Setup ---------------- -* Install PostgresSQL here at https://www.postgresql.org/download/ -* Login to postgres via command line by entering ``psql postgres`` -* Create the eatery database via ``create database "eatery-dev";`` -* Quit psql via ``\q`` -* Create an .envrc file and fill out the environment variables from the .envrctemplate file corresponding to your local postgres database -* To set up the tables, make sure current working directory is the src folder and run ``bash reset_db.sh`` -* To set up eatery data, run ``bash start.sh`` and hit the ``/cdn/populate/`` endpoint to populate the tables - -SP23 Members ---------------- + +--- + +- Install PostgresSQL here at https://www.postgresql.org/download/ +- Login to postgres via command line by entering `psql postgres` +- Create the eatery database via `create database "eatery-dev";` +- Quit psql via `\q` +- Create an `.envrc` file and fill out the environment variables from the `.envrctemplate` file corresponding to your local postgres database +- To set up the tables and data, make sure current working directory is the `src` folder and run `bash reset_db.sh` +- To run the backend, run `bash start.sh` + +## SP23 Members + - Mateo Weiner - Kidus Zegeye -FA22 Members ---------------- +## FA22 Members + - Marya Kim -- Sasha Loayza +- Sasha Loayza + +## SP22 Members -SP22 Members --------------- - Marya Kim - Archit Mehta diff --git a/src/reset_db.sh b/src/reset_db.sh index f68ba6e..2f4ca3c 100644 --- a/src/reset_db.sh +++ b/src/reset_db.sh @@ -1,4 +1,5 @@ source ../venv/bin/activate source ../.envrc python3 manage.py makemigrations -python3 manage.py migrate \ No newline at end of file +python3 manage.py migrate +python3 manage.py populate_models \ No newline at end of file From 366421375e9ec2f64e6c3b4141ad88e30cf3f3a5 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Mon, 20 Mar 2023 23:35:51 -0400 Subject: [PATCH 128/305] Replace scheduler with a cron job --- Dockerfile | 8 +++++++- docker-compose.server.yml | 2 +- docker-compose.yml | 2 +- requirements.txt | 1 - src/scheduler.py | 22 ---------------------- update_db.txt | 1 + 6 files changed, 10 insertions(+), 26 deletions(-) delete mode 100644 src/scheduler.py create mode 100644 update_db.txt diff --git a/Dockerfile b/Dockerfile index 0086eab..13d6821 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,13 @@ FROM python:3.9 +RUN apt-get update && apt-get -y install cron && apt-get -y install vim RUN mkdir /usr/app WORKDIR /usr/app COPY ./src . COPY ./requirements.txt . -RUN pip install -r requirements.txt \ No newline at end of file +RUN pip install -r requirements.txt + +COPY update_db.txt /etc/cron.d/update_db +RUN chmod 0644 /etc/cron.d/update_db +RUN chmod 0744 manage.py +RUN crontab /etc/cron.d/update_db diff --git a/docker-compose.server.yml b/docker-compose.server.yml index 04418ba..bd2d8b2 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -6,7 +6,7 @@ services: env_file: .env stdin_open: true # docker run -i tty: true # docker run -t - command: sh -c "python manage.py migrate && { python scheduler.py & python manage.py runserver 0.0.0.0:80; }" + command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron -f & python manage.py runserver 0.0.0.0:8; }" ports: - "8000:8000" restart: always diff --git a/docker-compose.yml b/docker-compose.yml index e75e73c..db4fbe1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: env_file: .env stdin_open: true # docker run -i tty: true # docker run -t - command: sh -c "python manage.py migrate && { python scheduler.py & python manage.py runserver 0.0.0.0:80; }" + command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron -f & python manage.py runserver 0.0.0.0:8; }" ports: - "80:80" restart: always diff --git a/requirements.txt b/requirements.txt index fdca582..d86038e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,6 @@ pytz==2021.3 requests==2.26.0 requests-oauthlib==1.3.0 rsa==4.8 -schedule==1.1.0 six==1.16.0 sqlparse==0.4.2 tomli==1.2.3 diff --git a/src/scheduler.py b/src/scheduler.py deleted file mode 100644 index 7c029fc..0000000 --- a/src/scheduler.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import django -from django.core import management -import schedule -import time - -try: - os.environ["DJANGO_SETTINGS_MODULE"] -except: - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") - django.setup() - -print("Scheduler started") -def populate_models(): - management.call_command('populate_models') - -schedule.every().day.do(populate_models) - -populate_models() -while True: - schedule.run_pending() - time.sleep(1) \ No newline at end of file diff --git a/update_db.txt b/update_db.txt new file mode 100644 index 0000000..4b28995 --- /dev/null +++ b/update_db.txt @@ -0,0 +1 @@ +0 8-21 * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /proc/1/fd/1 2>/proc/1/fd/2 From 05521c3e02ca285ace02c35ccfd932584d9eb836 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Thu, 23 Mar 2023 15:38:35 -0400 Subject: [PATCH 129/305] PR changes --- Dockerfile | 3 +-- docker-compose.server.yml | 2 +- docker-compose.yml | 2 +- src/eatery/controllers/populate_eatery.py | 2 -- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 13d6821..3eb052d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,5 @@ COPY ./requirements.txt . RUN pip install -r requirements.txt COPY update_db.txt /etc/cron.d/update_db -RUN chmod 0644 /etc/cron.d/update_db -RUN chmod 0744 manage.py +RUN chmod 0766 manage.py RUN crontab /etc/cron.d/update_db diff --git a/docker-compose.server.yml b/docker-compose.server.yml index bd2d8b2..74d9826 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -6,7 +6,7 @@ services: env_file: .env stdin_open: true # docker run -i tty: true # docker run -t - command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron -f & python manage.py runserver 0.0.0.0:8; }" + command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron -f & python manage.py runserver 0.0.0.0:8000; }" ports: - "8000:8000" restart: always diff --git a/docker-compose.yml b/docker-compose.yml index db4fbe1..20523d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: env_file: .env stdin_open: true # docker run -i tty: true # docker run -t - command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron -f & python manage.py runserver 0.0.0.0:8; }" + command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron -f & python manage.py runserver 0.0.0.0:80; }" ports: - "80:80" restart: always diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index 8e50100..aea5cbd 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -75,8 +75,6 @@ def add_eatery_store(self): if serialized.is_valid(): serialized.save() - #else: - #print(serialized.errors) def process(self, json_eateries): for json_eatery in json_eateries: From 9a13c5e69298e21949bea5301184104aefb0b54a Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 29 Mar 2023 17:46:15 -0400 Subject: [PATCH 130/305] Return unix timesamp --- src/eatery/controllers/populate_eatery.py | 4 ++-- src/event/controllers/populate_event.py | 18 ++---------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index aea5cbd..23f5da9 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -42,8 +42,8 @@ def generate_eatery(self, json_eatery): eatery = EaterySerializer(data=data) if eatery.is_valid(): eatery.save() - #else: - #print(eatery.errors) + else: + print(eatery.errors) def add_eatery_store(self): """ diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py index 6af6589..dcad6bb 100644 --- a/src/event/controllers/populate_event.py +++ b/src/event/controllers/populate_event.py @@ -6,19 +6,6 @@ class PopulateEventController(): def __init__(self): self = self - def create_event_datetime(self, json_event, date): - """ - merge date and timestamp for creating event. - return {'start': start, 'end': end} - """ - start_time = datetime.fromtimestamp(json_event["startTimestamp"]) - end_time = datetime.fromtimestamp(json_event["endTimestamp"]) - start = datetime.combine(date, start_time.time()) - end = datetime.combine(date, end_time.time()) - - return {"start" : start, "end": end} - - def generate_events(self, json_eatery): """ From an eatery json from CDN, create events for that eatery and add to event model. @@ -34,13 +21,12 @@ def generate_events(self, json_eatery): for json_event in json_events: # Create an event: - dates = self.create_event_datetime(json_event, canon_date) eatery_id = dining_id_to_internal_id(json_eatery["id"]).value data = { 'eatery': eatery_id, 'event_description': json_event["descr"], - 'start' : dates['start'], - 'end' : dates['end']} + 'start' : json_event["startTimestamp"], + 'end' : json_event["endTimestamp"]} event = EventSerializer(data=data) From 72855903a110ad15cc1508a8fb74889e2889dd80 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Fri, 31 Mar 2023 18:37:07 -0400 Subject: [PATCH 131/305] Implement chef and student model --- src/eatery_blue_backend/settings.py | 4 +- src/eatery_blue_backend/urls.py | 2 +- src/{user => person}/__init__.py | 0 src/person/admin.py | 5 ++ src/{user => person}/apps.py | 4 +- src/person/migrations/0001_initial.py | 29 ++++++++++++ ...user_student_favorite_eateries_and_more.py | 42 +++++++++++++++++ src/{user => person}/migrations/__init__.py | 0 src/person/models.py | 13 +++++ src/person/serializers.py | 13 +++++ src/{user => person}/tests.py | 0 src/{user => person}/urls.py | 5 +- src/person/views.py | 11 +++++ src/user/admin.py | 5 -- src/user/migrations/0001_initial.py | 47 ------------------- src/user/models.py | 9 ---- src/user/serializers.py | 9 ---- src/user/views.py | 7 --- 18 files changed, 120 insertions(+), 85 deletions(-) rename src/{user => person}/__init__.py (100%) create mode 100644 src/person/admin.py rename src/{user => person}/apps.py (64%) create mode 100644 src/person/migrations/0001_initial.py create mode 100644 src/person/migrations/0002_chef_user_student_favorite_eateries_and_more.py rename src/{user => person}/migrations/__init__.py (100%) create mode 100644 src/person/models.py create mode 100644 src/person/serializers.py rename src/{user => person}/tests.py (100%) rename src/{user => person}/urls.py (58%) create mode 100644 src/person/views.py delete mode 100644 src/user/admin.py delete mode 100644 src/user/migrations/0001_initial.py delete mode 100644 src/user/models.py delete mode 100644 src/user/serializers.py delete mode 100644 src/user/views.py diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 97c9440..712ae70 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -47,7 +47,7 @@ "category", "cdn_parser", "rest_framework", - "user", + "person", ] MIDDLEWARE = [ @@ -113,8 +113,6 @@ }, ] -AUTH_USER_MODEL = "user.User" - # Internationalization # https://docs.djangoproject.com/en/4.0/topics/i18n/ diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 26d5186..bf1a988 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -23,6 +23,6 @@ path("cdn/", include("cdn_parser.urls")), path("report/", include("report.urls")), path("alert/", include("alert.urls")), - path("user/", include("user.urls")), + path("person/", include("person.urls")), #path("wait_time/", include("wait_time.urls")) ] diff --git a/src/user/__init__.py b/src/person/__init__.py similarity index 100% rename from src/user/__init__.py rename to src/person/__init__.py diff --git a/src/person/admin.py b/src/person/admin.py new file mode 100644 index 0000000..8b935f3 --- /dev/null +++ b/src/person/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from person.models import Student, Chef + +admin.site.register(Student) +admin.site.register(Chef) \ No newline at end of file diff --git a/src/user/apps.py b/src/person/apps.py similarity index 64% rename from src/user/apps.py rename to src/person/apps.py index 36cce4c..b30fee4 100644 --- a/src/user/apps.py +++ b/src/person/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig -class UserConfig(AppConfig): +class PersonConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'user' + name = 'person' diff --git a/src/person/migrations/0001_initial.py b/src/person/migrations/0001_initial.py new file mode 100644 index 0000000..05d1e0c --- /dev/null +++ b/src/person/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 4.0 on 2023-03-31 22:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('eatery', '0003_alter_eatery_location_alter_eatery_menu_summary'), + ] + + operations = [ + migrations.CreateModel( + name='Student', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('netid', models.TextField()), + ], + ), + migrations.CreateModel( + name='Chef', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('eateries_managed', models.ManyToManyField(related_name='chefs', to='eatery.Eatery')), + ], + ), + ] diff --git a/src/person/migrations/0002_chef_user_student_favorite_eateries_and_more.py b/src/person/migrations/0002_chef_user_student_favorite_eateries_and_more.py new file mode 100644 index 0000000..1e99a13 --- /dev/null +++ b/src/person/migrations/0002_chef_user_student_favorite_eateries_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.0 on 2023-03-31 22:35 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('item', '0002_alter_item_base_price'), + ('eatery', '0003_alter_eatery_location_alter_eatery_menu_summary'), + ('auth', '0012_alter_user_first_name_max_length'), + ('person', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='chef', + name='user', + field=models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user'), + ), + migrations.AddField( + model_name='student', + name='favorite_eateries', + field=models.ManyToManyField(related_name='student', to='eatery.Eatery'), + ), + migrations.AddField( + model_name='student', + name='favorite_items', + field=models.ManyToManyField(related_name='student', to='item.Item'), + ), + migrations.AddField( + model_name='student', + name='user', + field=models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user'), + ), + migrations.AlterField( + model_name='chef', + name='eateries_managed', + field=models.ManyToManyField(related_name='chef', to='eatery.Eatery'), + ), + ] diff --git a/src/user/migrations/__init__.py b/src/person/migrations/__init__.py similarity index 100% rename from src/user/migrations/__init__.py rename to src/person/migrations/__init__.py diff --git a/src/person/models.py b/src/person/models.py new file mode 100644 index 0000000..4542c64 --- /dev/null +++ b/src/person/models.py @@ -0,0 +1,13 @@ +from django.db import models +from django.contrib.auth.models import User + + +class Student(models.Model): + netid = models.TextField() + favorite_eateries = models.ManyToManyField('eatery.Eatery', related_name='student') + favorite_items = models.ManyToManyField('item.Item', related_name='student') + user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) + +class Chef(models.Model): + eateries_managed = models.ManyToManyField('eatery.Eatery', related_name='chef') + user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) \ No newline at end of file diff --git a/src/person/serializers.py b/src/person/serializers.py new file mode 100644 index 0000000..b0bf8d5 --- /dev/null +++ b/src/person/serializers.py @@ -0,0 +1,13 @@ +from rest_framework import serializers + +from person.models import Student, Chef + +class StudentSerializer(serializers.ModelSerializer): + class Meta: + model = Student + fields = ['id', 'netid', 'user', 'favorite_eateries', 'favorite_items'] + +class ChefSerializer(serializers.ModelSerializer): + class Meta: + model = Chef + fields = ['id', 'user', 'eateries_managed'] diff --git a/src/user/tests.py b/src/person/tests.py similarity index 100% rename from src/user/tests.py rename to src/person/tests.py diff --git a/src/user/urls.py b/src/person/urls.py similarity index 58% rename from src/user/urls.py rename to src/person/urls.py index a60cd47..cef5660 100644 --- a/src/user/urls.py +++ b/src/person/urls.py @@ -1,9 +1,10 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from user import views +from person import views router = DefaultRouter() -router.register(r'', views.UserViewSet) +router.register(r'student', views.StudentViewSet) +router.register(r'chef', views.ChefViewSet) urlpatterns = [ path('', include(router.urls)) diff --git a/src/person/views.py b/src/person/views.py new file mode 100644 index 0000000..bc04b81 --- /dev/null +++ b/src/person/views.py @@ -0,0 +1,11 @@ +from rest_framework import viewsets +from person.models import Student, Chef +from person.serializers import StudentSerializer, ChefSerializer + +class StudentViewSet(viewsets.ModelViewSet): + queryset = Student.objects.all() + serializer_class = StudentSerializer + +class ChefViewSet(viewsets.ModelViewSet): + queryset = Chef.objects.all() + serializer_class = ChefSerializer diff --git a/src/user/admin.py b/src/user/admin.py deleted file mode 100644 index 888bc72..0000000 --- a/src/user/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin -from django.contrib.auth.admin import UserAdmin -from .models import User - -admin.site.register(User, UserAdmin) \ No newline at end of file diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py deleted file mode 100644 index 6afe154..0000000 --- a/src/user/migrations/0001_initial.py +++ /dev/null @@ -1,47 +0,0 @@ -# Generated by Django 4.0 on 2023-03-15 18:41 - -import django.contrib.auth.models -import django.contrib.auth.validators -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('item', '0002_alter_item_base_price'), - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='User', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('netid', models.TextField(unique=True)), - ('favorite_items', models.ManyToManyField(blank=True, related_name='users', to='item.Item')), - ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), - ], - options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), - ] diff --git a/src/user/models.py b/src/user/models.py deleted file mode 100644 index 1fc1b24..0000000 --- a/src/user/models.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.contrib.auth.models import AbstractUser -from django.db import models - -class User(AbstractUser): - netid = models.TextField(unique=True) - favorite_items = models.ManyToManyField('item.Item', related_name='users', blank=True) - -def __str__(self): - return self.netid \ No newline at end of file diff --git a/src/user/serializers.py b/src/user/serializers.py deleted file mode 100644 index 429639d..0000000 --- a/src/user/serializers.py +++ /dev/null @@ -1,9 +0,0 @@ -from rest_framework import serializers - -from user.models import User - -class UserSerializer(serializers.ModelSerializer): - class Meta: - model = User - fields = ['url', 'id', 'username', 'first_name', 'last_name', 'email', - 'is_staff', 'is_active', 'date_joined', 'netid', 'favorite_items'] diff --git a/src/user/views.py b/src/user/views.py deleted file mode 100644 index 499368e..0000000 --- a/src/user/views.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import viewsets -from user.models import User -from user.serializers import UserSerializer - -class UserViewSet(viewsets.ModelViewSet): - queryset = User.objects.all() - serializer_class = UserSerializer \ No newline at end of file From 63f92f19d16f2cb0888f47fceabe4cff4b0a49ac Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Mon, 3 Apr 2023 23:35:40 -0500 Subject: [PATCH 132/305] Implement simpler eatery route --- src/eatery/serializers.py | 12 +++++++++++- src/eatery/urls.py | 3 ++- src/eatery/views.py | 9 ++++++++- src/event/serializers.py | 5 +++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 6cb95c6..a84973e 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from eatery.models import Eatery -from event.serializers import EventSerializer +from event.serializers import EventSerializer, EventSerializerSimple class EaterySerializer(serializers.ModelSerializer): @@ -26,3 +26,13 @@ def create(self, validated_data): class Meta: model = Eatery fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + + +class EaterySerializerSimple(serializers.ModelSerializer): + menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") + image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + events = EventSerializerSimple(many=True, read_only=True) + + class Meta: + model = Eatery + fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] diff --git a/src/eatery/urls.py b/src/eatery/urls.py index 71b9cf6..bd3d014 100644 --- a/src/eatery/urls.py +++ b/src/eatery/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from eatery.views import EateryViewSet +from eatery.views import EateryViewSet, EateryViewSetSimple eateries_list = EateryViewSet.as_view({ 'get':'list', @@ -16,4 +16,5 @@ urlpatterns = [ path("", eateries_list, name='eateries-list'), path("/", eatery_list, name='eatery-list'), + path("simple/", EateryViewSetSimple.as_view({'get':'list'}), name='eateries-simple'), ] \ No newline at end of file diff --git a/src/eatery/views.py b/src/eatery/views.py index 1c511a8..e98443e 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -1,4 +1,4 @@ -from eatery.serializers import EaterySerializer +from eatery.serializers import EaterySerializer, EaterySerializerSimple from eatery.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse from django.shortcuts import get_object_or_404 @@ -90,3 +90,10 @@ def get(self, request): return JsonResponse(error_json("eateries is empty")) return JsonResponse(success_json(eateries.data)) + +class EateryViewSetSimple(viewsets.ModelViewSet): + """ + View all eateries with less information + """ + queryset = Eatery.objects.all() + serializer_class = EaterySerializerSimple \ No newline at end of file diff --git a/src/event/serializers.py b/src/event/serializers.py index 9702ec9..cb9af3f 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -20,3 +20,8 @@ def create(self, validated_data): class Meta: model = Event fields = ["id", "eatery", "event_description", "start", "end", "menu"] + +class EventSerializerSimple(serializers.ModelSerializer): + class Meta: + model = Event + fields = ["id", "eatery", "event_description", "start", "end"] From 8158dc61fe08953950929a9ad293c6cdd37bcbdd Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 4 Apr 2023 21:37:40 -0500 Subject: [PATCH 133/305] Fix thumbnail issues and make event times into timestamps --- src/eatery/controllers/populate_eatery.py | 8 ++- src/eatery/util/eatery_store.txt | 77 ++++++++++++----------- src/event/serializers.py | 8 ++- src/reset_db.sh | 1 - 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index aea5cbd..1f51e6e 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -42,8 +42,8 @@ def generate_eatery(self, json_eatery): eatery = EaterySerializer(data=data) if eatery.is_valid(): eatery.save() - #else: - #print(eatery.errors) + else: + print(eatery.errors) def add_eatery_store(self): """ @@ -58,7 +58,7 @@ def add_eatery_store(self): for line in file: if len(line) > 2: json_objs.append(json.loads(line)) - + for json_obj in json_objs: try: object = Eatery.objects.get(id=int(json_obj["id"])) @@ -75,6 +75,8 @@ def add_eatery_store(self): if serialized.is_valid(): serialized.save() + else: + print(serialized.errors) def process(self, json_eateries): for json_eatery in json_eateries: diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index bde75e7..4f20ab2 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -1,38 +1,39 @@ -{"id": 1, "name": "", "menu_summary": "Halal food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 2, "name": "", "menu_summary": "Limes, corona, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 3, "name": "", "menu_summary": "Food, drink", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 4, "name": "", "menu_summary": "Quick bites and convenient food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 5, "name": "", "menu_summary": "Cleanest dining hall on campus", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 6, "name": "", "menu_summary": "Sandwiches, salads, hay", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 7, "name": "", "menu_summary": "Bagels, bagels, bagels", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 8, "name": "", "menu_summary": "Sandwiches, salads, potatoes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 9, "name": "", "menu_summary": "Carols, wine, christmas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Carols-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 10, "name": "", "menu_summary": "Well cooked food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 11, "name": "", "menu_summary": "Wraps, pizza, cookies", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 12, "name": "", "menu_summary": "Tea, pizza, wine", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 13, "name": "", "menu_summary": "Fries, burgers, and shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 14, "name": "", "menu_summary": "Sandwiches, salads, goldfish", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 15, "name": "", "menu_summary": "Fire, scales, green dragons", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 16, "name": "", "menu_summary": "Burgers, burritos, quesadillas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 17, "name": "", "menu_summary": "Ice cream, slushies, and fried dough", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 18, "name": "", "menu_summary": "Cookies, cereal, chicken pot pie", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 19, "name": "", "menu_summary": "Convenience items, milk", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 20, "name": "", "menu_summary": "Meat, salads, ice cream", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 21, "name": "", "menu_summary": "Sandwiches, soups, croissants", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 22, "name": "", "menu_summary": "Pollack specials, chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 23, "name": "", "menu_summary": "Sandwiches, soups, sushi", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 24, "name": "", "menu_summary": "Corn, vegetables, bread", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 25, "name": "", "menu_summary": "A historic dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 26, "name": "", "menu_summary": "The only central campus dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 27, "name": "", "menu_summary": "Vegan dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 28, "name": "", "menu_summary": "Sushi Fridays, freshly made pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/RPCC-Marketplace.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 29, "name": "", "menu_summary": "Taco tuesday, pizza, noodles", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 30, "name": "", "menu_summary": "Coffee, tea, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 31, "name": "", "menu_summary": "Falafel, rice, beans", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 32, "name": "", "menu_summary": "Fries, salads, fried chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg", "location": "", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 33, "name": "Terrace", "menu_summary": "Burrito and rice bowls, pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg", "location": "Statler Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.446267, "longitude": -76.482314, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true} -{"id": 34, "name": "Mac's Caf\u00e9", "menu_summary": "Flatbreads, salads, pasta", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/macs", "location": "Statler Hotel", "campus_area": "Central", "online_order_url": "", "latitude": 42.445921, "longitude": -76.481984, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true} -{"id": 35, "name": "Temple of Zeus", "menu_summary": "Coffee, pastries, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Zeus.jpg", "location": "Goldwin Smith Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.449091, "longitude": -76.483414, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} -{"id": 36, "name": "Gimme Coffee", "menu_summary": "Coffee, pastries, tea", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Gimme-Coffee.jpg", "location": "Gates Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.444958, "longitude": -76.481169, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} -{"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "online_order_url": "", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} -{"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} +{"id": 1, "menu_summary": "Halal food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 2, "menu_summary": "Limes, corona, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 3, "menu_summary": "Food, drink", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 4, "menu_summary": "Quick bites and convenient food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 5, "menu_summary": "Cleanest dining hall on campus", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 6, "menu_summary": "Sandwiches, salads, hay", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 7, "menu_summary": "Bagels, bagels, bagels", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 8, "menu_summary": "Sandwiches, salads, potatoes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 9, "name": "Carol's Caf\u00e9", "menu_summary": "Carols, wine, christmas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Carols-Cafe.jpg", "location": "Balch Hall", "campus_area": "North", "latitude": 42.4533011, "longitude": -76.4791678, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": false, "online_order_url": null} +{"id": 10, "menu_summary": "Well cooked food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 11, "menu_summary": "Wraps, pizza, cookies", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 12, "menu_summary": "Tea, pizza, wine", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 13, "menu_summary": "Fries, burgers, and shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 14, "menu_summary": "Sandwiches, salads, goldfish", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 15, "menu_summary": "Fire, scales, green dragons", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 16, "menu_summary": "Burgers, burritos, quesadillas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 17, "menu_summary": "Ice cream, slushies, and fried dough", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 18, "menu_summary": "Cookies, cereal, chicken pot pie", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 19, "menu_summary": "Convenience items, milk", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 20, "menu_summary": "Meat, salads, ice cream", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 21, "menu_summary": "Sandwiches, soups, croissants", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 22, "menu_summary": "Pollack specials, chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 23, "menu_summary": "Sandwiches, soups, sushi", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 24, "menu_summary": "Corn, vegetables, bread", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 25, "menu_summary": "A historic dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 26, "menu_summary": "The only central campus dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 27, "menu_summary": "Vegan dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 28, "menu_summary": "Sushi Fridays, freshly made pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/RPCC-Marketplace.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 29, "menu_summary": "Taco tuesday, pizza, noodles", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 30, "menu_summary": "Coffee, tea, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 31, "menu_summary": "Falafel, rice, beans", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 32, "menu_summary": "Fries, salads, fried chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 33, "name": "Terrace", "menu_summary": "Burrito and rice bowls, pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg", "location": "Statler Hall", "campus_area": "Central", "latitude": 42.446267, "longitude": -76.482314, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} +{"id": 34, "name": "Mac's Caf\u00e9", "menu_summary": "Flatbreads, salads, pasta", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/macs", "location": "Statler Hotel", "campus_area": "Central", "latitude": 42.445921, "longitude": -76.481984, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} +{"id": 35, "name": "Temple of Zeus", "menu_summary": "Coffee, pastries, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Zeus.jpg", "location": "Goldwin Smith Hall", "campus_area": "Central", "latitude": 42.449091, "longitude": -76.483414, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} +{"id": 36, "name": "Gimme Coffee", "menu_summary": "Coffee, pastries, tea", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Gimme-Coffee.jpg", "location": "Gates Hall", "campus_area": "Central", "latitude": 42.444958, "longitude": -76.481169, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} +{"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} +{"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} +{"id": 39, "menu_summary": "Pizza, pasta, wok, halal and kosher stations", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} diff --git a/src/event/serializers.py b/src/event/serializers.py index 9702ec9..84346a8 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers from event.models import Event from category.serializers import CategorySerializer - +from datetime import datetime class EventSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False, read_only=True) @@ -20,3 +20,9 @@ def create(self, validated_data): class Meta: model = Event fields = ["id", "eatery", "event_description", "start", "end", "menu"] + + def to_representation(self, instance): + ret = super().to_representation(instance) + ret['start'] =round(datetime.strptime(ret['start'],'%Y-%m-%dT%H:%M:%S%z').timestamp()) + ret['end'] = round(datetime.strptime(ret['end'],'%Y-%m-%dT%H:%M:%S%z').timestamp()) + return ret diff --git a/src/reset_db.sh b/src/reset_db.sh index 2f4ca3c..8c98f47 100644 --- a/src/reset_db.sh +++ b/src/reset_db.sh @@ -2,4 +2,3 @@ source ../venv/bin/activate source ../.envrc python3 manage.py makemigrations python3 manage.py migrate -python3 manage.py populate_models \ No newline at end of file From 712d83852db338b2445ca59297a88bbcb919e6d2 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 5 Apr 2023 01:29:56 -0500 Subject: [PATCH 134/305] Use json timestamp --- ...005_remove_event_end_remove_event_start.py | 21 +++++++++++++++++ .../migrations/0006_event_end_event_start.py | 23 +++++++++++++++++++ src/event/models.py | 4 ++-- src/event/serializers.py | 11 +++------ 4 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 src/event/migrations/0005_remove_event_end_remove_event_start.py create mode 100644 src/event/migrations/0006_event_end_event_start.py diff --git a/src/event/migrations/0005_remove_event_end_remove_event_start.py b/src/event/migrations/0005_remove_event_end_remove_event_start.py new file mode 100644 index 0000000..47d2737 --- /dev/null +++ b/src/event/migrations/0005_remove_event_end_remove_event_start.py @@ -0,0 +1,21 @@ +# Generated by Django 4.0 on 2023-04-05 06:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0004_alter_event_event_description'), + ] + + operations = [ + migrations.RemoveField( + model_name='event', + name='end', + ), + migrations.RemoveField( + model_name='event', + name='start', + ), + ] diff --git a/src/event/migrations/0006_event_end_event_start.py b/src/event/migrations/0006_event_end_event_start.py new file mode 100644 index 0000000..9ed44fd --- /dev/null +++ b/src/event/migrations/0006_event_end_event_start.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0 on 2023-04-05 06:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0005_remove_event_end_remove_event_start'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='end', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='event', + name='start', + field=models.IntegerField(default=0), + ), + ] diff --git a/src/event/models.py b/src/event/models.py index 68c5066..24a9086 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -16,5 +16,5 @@ class Event(models.Model): eatery = models.ForeignKey(Eatery, related_name = "events", on_delete=models.DO_NOTHING) event_description = models.TextField( choices=EventDescription.choices, default = EventDescription.GENERAL, blank=True, null = True) - start = models.DateTimeField() - end = models.DateTimeField() + start = models.IntegerField(default = 0) + end = models.IntegerField(default = 0) diff --git a/src/event/serializers.py b/src/event/serializers.py index 84346a8..75f8841 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -8,8 +8,8 @@ class EventSerializer(serializers.ModelSerializer): event_description = serializers.CharField( allow_null=True, allow_blank=True, default=None ) - start = serializers.DateTimeField() - end = serializers.DateTimeField() + start = serializers.IntegerField() + end = serializers.IntegerField() menu = CategorySerializer(many=True, read_only=True) @@ -20,9 +20,4 @@ def create(self, validated_data): class Meta: model = Event fields = ["id", "eatery", "event_description", "start", "end", "menu"] - - def to_representation(self, instance): - ret = super().to_representation(instance) - ret['start'] =round(datetime.strptime(ret['start'],'%Y-%m-%dT%H:%M:%S%z').timestamp()) - ret['end'] = round(datetime.strptime(ret['end'],'%Y-%m-%dT%H:%M:%S%z').timestamp()) - return ret + From 1cfdbce3dc36e545d1dda4a838a0c12094d60df7 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 5 Apr 2023 15:55:24 -0500 Subject: [PATCH 135/305] remove menu model --- src/category/controllers/populate_category.py | 27 ++++++------ src/category/migrations/0001_initial.py | 6 +-- .../migrations/0002_alter_category_menu.py | 20 --------- .../migrations/0003_alter_category_menu.py | 21 ---------- ...004_remove_category_menu_category_event.py | 25 ----------- .../migrations/0005_alter_category_event.py | 20 --------- src/cdn_parser/controllers/populate_models.py | 17 +++++--- src/eatery_blue_backend/settings.py | 1 - src/item/migrations/0001_initial.py | 4 +- .../migrations/0002_alter_item_base_price.py | 18 -------- src/menu/__init__.py | 0 src/menu/admin.py | 4 -- src/menu/apps.py | 6 --- src/menu/controllers/populate_menu.py | 42 ------------------- src/menu/migrations/0001_initial.py | 23 ---------- src/menu/migrations/__init__.py | 0 src/menu/models.py | 6 --- src/menu/serializers.py | 15 ------- src/menu/tests.py | 3 -- src/menu/views.py | 3 -- src/user/migrations/0001_initial.py | 4 +- 21 files changed, 32 insertions(+), 233 deletions(-) delete mode 100644 src/category/migrations/0002_alter_category_menu.py delete mode 100644 src/category/migrations/0003_alter_category_menu.py delete mode 100644 src/category/migrations/0004_remove_category_menu_category_event.py delete mode 100644 src/category/migrations/0005_alter_category_event.py delete mode 100644 src/item/migrations/0002_alter_item_base_price.py delete mode 100644 src/menu/__init__.py delete mode 100644 src/menu/admin.py delete mode 100644 src/menu/apps.py delete mode 100644 src/menu/controllers/populate_menu.py delete mode 100644 src/menu/migrations/0001_initial.py delete mode 100644 src/menu/migrations/__init__.py delete mode 100644 src/menu/models.py delete mode 100644 src/menu/serializers.py delete mode 100644 src/menu/tests.py delete mode 100644 src/menu/views.py diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index f520b16..e13b39b 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -13,13 +13,13 @@ class PopulateCategoryController: def __init__(self): self = self - def generate_dining_hall_categories(self, json_event, menu): + def generate_dining_hall_categories(self, json_event, event): """ category_items = {"category_name" : id, ... } """ category_items = {} for json_menu in json_event["menu"]: - data = {"event": int(menu.data["id"]), "category": json_menu["category"]} + data = {"event": event, "category": json_menu["category"]} category = CategorySerializer(data=data) if category.is_valid(): @@ -33,7 +33,7 @@ def generate_dining_hall_categories(self, json_event, menu): return category_items - def generate_cafe_categories(self, json_eatery, menu): + def generate_cafe_categories(self, json_eatery, event): """ category_items = {"category_name" : id, ... } """ @@ -44,7 +44,7 @@ def generate_cafe_categories(self, json_eatery, menu): for item in dining_items: if item["category"] not in categories: categories.append(item["category"]) - data = {"event": int(menu.data["id"]), "category": item["category"]} + data = {"event": event, "category": item["category"]} category = CategorySerializer(data=data) if category.is_valid(): category.save() @@ -57,7 +57,7 @@ def generate_cafe_categories(self, json_eatery, menu): return category_items - def process(self, menus_dict, json_eateries): + def process(self, events_dict, json_eateries): """categories_dict = { eatery_id : { event[i] : {"category_name" : id, "category_name" : id}, event[i] : {"category_name" : id} @@ -70,8 +70,8 @@ def process(self, menus_dict, json_eateries): eatery_id = int(json_eatery["id"]) categories_dict[eatery_id] = {} - if eatery_id in menus_dict: - eatery_menus = menus_dict[eatery_id] + if eatery_id in events_dict: + eatery_events = events_dict[eatery_id] i = 0 else: continue @@ -87,21 +87,20 @@ def process(self, menus_dict, json_eateries): for json_date in json_dates: json_events = json_date["events"] for json_event in json_events: - if i < len(eatery_menus): - menu = eatery_menus[i] + if i < len(eatery_events): + event = eatery_events[i] i += 1 - - categories_dict[eatery_id][menu.data["id"]] = {} + categories_dict[eatery_id][event] = {} if is_cafe: categories = self.generate_cafe_categories( - json_eatery, menu + json_eatery, event ) else: categories = self.generate_dining_hall_categories( - json_event, menu + json_event, event ) - categories_dict[eatery_id][menu.data["id"]] = categories + categories_dict[eatery_id][event] = categories return categories_dict diff --git a/src/category/migrations/0001_initial.py b/src/category/migrations/0001_initial.py index 4d4964f..3935446 100644 --- a/src/category/migrations/0001_initial.py +++ b/src/category/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2022-11-14 23:56 +# Generated by Django 4.0 on 2023-04-05 20:49 from django.db import migrations, models import django.db.models.deletion @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('menu', '0001_initial'), + ('event', '0006_event_end_event_start'), ] operations = [ @@ -18,7 +18,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), ('category', models.CharField(default='General', max_length=40)), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='categories', to='menu.menu')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='menu', to='event.event')), ], ), ] diff --git a/src/category/migrations/0002_alter_category_menu.py b/src/category/migrations/0002_alter_category_menu.py deleted file mode 100644 index 551e819..0000000 --- a/src/category/migrations/0002_alter_category_menu.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.0 on 2022-11-15 22:31 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('menu', '0001_initial'), - ('category', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='category', - name='menu', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='categories', to='menu.menu'), - ), - ] diff --git a/src/category/migrations/0003_alter_category_menu.py b/src/category/migrations/0003_alter_category_menu.py deleted file mode 100644 index b3dad03..0000000 --- a/src/category/migrations/0003_alter_category_menu.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.0 on 2022-11-15 22:36 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('menu', '0001_initial'), - ('category', '0002_alter_category_menu'), - ] - - operations = [ - migrations.AlterField( - model_name='category', - name='menu', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, related_name='categories', to='menu.menu'), - preserve_default=False, - ), - ] diff --git a/src/category/migrations/0004_remove_category_menu_category_event.py b/src/category/migrations/0004_remove_category_menu_category_event.py deleted file mode 100644 index 8d432c2..0000000 --- a/src/category/migrations/0004_remove_category_menu_category_event.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 4.0 on 2023-03-01 20:24 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('event', '0002_alter_event_end_alter_event_start'), - ('category', '0003_alter_category_menu'), - ] - - operations = [ - migrations.RemoveField( - model_name='category', - name='menu', - ), - migrations.AddField( - model_name='category', - name='event', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.DO_NOTHING, related_name='categories', to='event.event'), - preserve_default=False, - ), - ] diff --git a/src/category/migrations/0005_alter_category_event.py b/src/category/migrations/0005_alter_category_event.py deleted file mode 100644 index d11a4da..0000000 --- a/src/category/migrations/0005_alter_category_event.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.0 on 2023-03-08 22:57 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('event', '0002_alter_event_end_alter_event_start'), - ('category', '0004_remove_category_menu_category_event'), - ] - - operations = [ - migrations.AlterField( - model_name='category', - name='event', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='menu', to='event.event'), - ), - ] diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py index 0d15cde..e731432 100644 --- a/src/cdn_parser/controllers/populate_models.py +++ b/src/cdn_parser/controllers/populate_models.py @@ -1,9 +1,10 @@ import requests from eatery.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id +from django.core import management from eatery.controllers.populate_eatery import PopulateEateryController from event.controllers.populate_event import PopulateEventController -from menu.controllers.populate_menu import PopulateMenuController +#from menu.controllers.populate_menu import PopulateMenuController from item.controllers.populate_item import PopulateItemController from category.controllers.populate_category import PopulateCategoryController """ @@ -49,19 +50,25 @@ def process(self): """ json_eateries = self.get_json() + print("Populating eateries") + #management.call_command('migrate', 'eatery', 'zero') + #management.call_command('migrate', 'eatery') PopulateEateryController().process(json_eateries) print("Populating events") + #management.call_command('migrate', 'event', 'zero') + #management.call_command('migrate', 'event') events_dict = PopulateEventController().process(json_eateries) - print("Populating menus") - menus_dict = PopulateMenuController().process(events_dict, json_eateries) - print("Populating categories") - categories_dict = PopulateCategoryController().process(menus_dict, json_eateries) + #management.call_command('migrate', 'category', 'zero') + #management.call_command('migrate', 'category') + categories_dict = PopulateCategoryController().process(events_dict, json_eateries) print("Populating items") + #management.call_command('migrate', 'item', 'zero') + #management.call_command('migrate', 'item') PopulateItemController().process(categories_dict, json_eateries) print("Done populating") diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 97c9440..baaf76e 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -42,7 +42,6 @@ "alert", "event", "report", - "menu", "item", "category", "cdn_parser", diff --git a/src/item/migrations/0001_initial.py b/src/item/migrations/0001_initial.py index 28b39e0..65f15d7 100644 --- a/src/item/migrations/0001_initial.py +++ b/src/item/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2022-11-14 23:56 +# Generated by Django 4.0 on 2023-04-05 20:49 from django.db import migrations, models import django.db.models.deletion @@ -18,7 +18,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), ('name', models.CharField(default='Item', max_length=40)), - ('base_price', models.FloatField(blank=True, null=True)), + ('base_price', models.FloatField(blank=True, default=0.0, null=True)), ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='items', to='category.category')), ], ), diff --git a/src/item/migrations/0002_alter_item_base_price.py b/src/item/migrations/0002_alter_item_base_price.py deleted file mode 100644 index 299f09a..0000000 --- a/src/item/migrations/0002_alter_item_base_price.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0 on 2022-11-15 23:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('item', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='item', - name='base_price', - field=models.FloatField(blank=True, default=0.0, null=True), - ), - ] diff --git a/src/menu/__init__.py b/src/menu/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/menu/admin.py b/src/menu/admin.py deleted file mode 100644 index e8ac683..0000000 --- a/src/menu/admin.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.contrib import admin -from menu.models import Menu - -admin.site.register(Menu) \ No newline at end of file diff --git a/src/menu/apps.py b/src/menu/apps.py deleted file mode 100644 index 88512e8..0000000 --- a/src/menu/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class MenuConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'menu' diff --git a/src/menu/controllers/populate_menu.py b/src/menu/controllers/populate_menu.py deleted file mode 100644 index 579bb93..0000000 --- a/src/menu/controllers/populate_menu.py +++ /dev/null @@ -1,42 +0,0 @@ -from menu.serializers import MenuSerializer - -class PopulateMenuController(): - def __init__(self): - self = self - menus = [] - - def generate_menu(self, json_event, event): - data = { - "event" : event #int(event.data["id"]) - } - menu = MenuSerializer(data=data) - if menu.is_valid(): - menu.save() - else: - print(menu.errors) - return menu - - - - def process(self, events_dict, json_eateries): - """ - menus_dict = { eatery_id : [menu, menu, menu...] } - """ - menus_dict = {} - - for json_eatery in json_eateries: - eatery_events = events_dict[int(json_eatery["id"])]; i=0 - json_dates = json_eatery["operatingHours"] - - menus_dict[int(json_eatery["id"])] = [] - - for json_date in json_dates: - json_events = json_date["events"] - for json_event in json_events: - event = eatery_events[i]; i+=1 - menu = self.generate_menu(json_event, event) - menus_dict[int(json_eatery["id"])].append(menu) - - return menus_dict - - diff --git a/src/menu/migrations/0001_initial.py b/src/menu/migrations/0001_initial.py deleted file mode 100644 index 89d16f8..0000000 --- a/src/menu/migrations/0001_initial.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.0 on 2022-11-14 23:56 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('event', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Menu', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='menus', to='event.event')), - ], - ), - ] diff --git a/src/menu/migrations/__init__.py b/src/menu/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/menu/models.py b/src/menu/models.py deleted file mode 100644 index f35fd4d..0000000 --- a/src/menu/models.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.db import models -from event.models import Event - -class Menu(models.Model): - id = models.AutoField(primary_key=True) - event = models.ForeignKey(Event, related_name="menus", on_delete = models.DO_NOTHING) diff --git a/src/menu/serializers.py b/src/menu/serializers.py deleted file mode 100644 index 1a397dd..0000000 --- a/src/menu/serializers.py +++ /dev/null @@ -1,15 +0,0 @@ -from rest_framework import serializers -from menu.models import Menu -from category.serializers import CategorySerializer - -class MenuSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(read_only = True) - categories = CategorySerializer(many=True, read_only=True) - - def create(self, validated_data): - menu, _ = Menu.objects.get_or_create(**validated_data) - return menu - - class Meta: - model = Menu - fields = ['id', 'event', 'categories'] \ No newline at end of file diff --git a/src/menu/tests.py b/src/menu/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/menu/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/menu/views.py b/src/menu/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/src/menu/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py index 6afe154..066e169 100644 --- a/src/user/migrations/0001_initial.py +++ b/src/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2023-03-15 18:41 +# Generated by Django 4.0 on 2023-04-05 20:49 import django.contrib.auth.models import django.contrib.auth.validators @@ -11,7 +11,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('item', '0002_alter_item_base_price'), + ('item', '0001_initial'), ('auth', '0012_alter_user_first_name_max_length'), ] From e5b437d538ba8aec9a3dbbb9a01bf152d0b9e6cb Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 5 Apr 2023 16:39:41 -0500 Subject: [PATCH 136/305] Implement database trunctate when populating --- src/cdn_parser/controllers/populate_models.py | 14 ++-- src/eatery/models.py | 6 ++ src/eatery/util/eatery_store.txt | 64 +++++++++---------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py index e731432..1eeaefe 100644 --- a/src/cdn_parser/controllers/populate_models.py +++ b/src/cdn_parser/controllers/populate_models.py @@ -1,7 +1,7 @@ import requests from eatery.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id from django.core import management - +from eatery.models import Eatery from eatery.controllers.populate_eatery import PopulateEateryController from event.controllers.populate_event import PopulateEventController #from menu.controllers.populate_menu import PopulateMenuController @@ -50,26 +50,20 @@ def process(self): """ json_eateries = self.get_json() - + + Eatery.truncate() print("Populating eateries") - #management.call_command('migrate', 'eatery', 'zero') - #management.call_command('migrate', 'eatery') PopulateEateryController().process(json_eateries) print("Populating events") - #management.call_command('migrate', 'event', 'zero') - #management.call_command('migrate', 'event') events_dict = PopulateEventController().process(json_eateries) print("Populating categories") - #management.call_command('migrate', 'category', 'zero') - #management.call_command('migrate', 'category') categories_dict = PopulateCategoryController().process(events_dict, json_eateries) print("Populating items") - #management.call_command('migrate', 'item', 'zero') - #management.call_command('migrate', 'item') PopulateItemController().process(categories_dict, json_eateries) + print("Done populating") diff --git a/src/eatery/models.py b/src/eatery/models.py index d23b86f..27cef9c 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.db import connection class Eatery(models.Model): @@ -23,3 +24,8 @@ class CampusArea(models.TextChoices): payment_accepts_meal_swipes = models.BooleanField(null=True, blank=True) payment_accepts_brbs = models.BooleanField(null=True, blank=True) payment_accepts_cash = models.BooleanField(null=True, blank=True) + + @classmethod + def truncate(cls): + with connection.cursor() as cursor: + cursor.execute('TRUNCATE TABLE {} CASCADE'.format(cls._meta.db_table)) \ No newline at end of file diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index 4f20ab2..f9f1569 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -1,39 +1,39 @@ -{"id": 1, "menu_summary": "Halal food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 2, "menu_summary": "Limes, corona, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 3, "menu_summary": "Food, drink", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 4, "menu_summary": "Quick bites and convenient food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 5, "menu_summary": "Cleanest dining hall on campus", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 6, "menu_summary": "Sandwiches, salads, hay", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 7, "menu_summary": "Bagels, bagels, bagels", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 8, "menu_summary": "Sandwiches, salads, potatoes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 1, "menu_summary": "Halal food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg"} +{"id": 2, "menu_summary": "Limes, corona, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg"} +{"id": 3, "menu_summary": "Food, drink", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg"} +{"id": 4, "menu_summary": "Quick bites and convenient food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg"} +{"id": 5, "menu_summary": "Cleanest dining hall on campus", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg"} +{"id": 6, "menu_summary": "Sandwiches, salads, hay", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg"} +{"id": 7, "menu_summary": "Bagels, bagels, bagels", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg"} +{"id": 8, "menu_summary": "Sandwiches, salads, potatoes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg"} {"id": 9, "name": "Carol's Caf\u00e9", "menu_summary": "Carols, wine, christmas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Carols-Cafe.jpg", "location": "Balch Hall", "campus_area": "North", "latitude": 42.4533011, "longitude": -76.4791678, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": false, "online_order_url": null} -{"id": 10, "menu_summary": "Well cooked food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 11, "menu_summary": "Wraps, pizza, cookies", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 12, "menu_summary": "Tea, pizza, wine", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 13, "menu_summary": "Fries, burgers, and shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 14, "menu_summary": "Sandwiches, salads, goldfish", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 15, "menu_summary": "Fire, scales, green dragons", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 16, "menu_summary": "Burgers, burritos, quesadillas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 17, "menu_summary": "Ice cream, slushies, and fried dough", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 18, "menu_summary": "Cookies, cereal, chicken pot pie", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 19, "menu_summary": "Convenience items, milk", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 20, "menu_summary": "Meat, salads, ice cream", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 21, "menu_summary": "Sandwiches, soups, croissants", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 22, "menu_summary": "Pollack specials, chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 23, "menu_summary": "Sandwiches, soups, sushi", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 24, "menu_summary": "Corn, vegetables, bread", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 25, "menu_summary": "A historic dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 26, "menu_summary": "The only central campus dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 27, "menu_summary": "Vegan dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 28, "menu_summary": "Sushi Fridays, freshly made pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/RPCC-Marketplace.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 29, "menu_summary": "Taco tuesday, pizza, noodles", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 30, "menu_summary": "Coffee, tea, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 31, "menu_summary": "Falafel, rice, beans", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} -{"id": 32, "menu_summary": "Fries, salads, fried chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 10, "menu_summary": "Well cooked food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg"} +{"id": 11, "menu_summary": "Wraps, pizza, cookies", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg"} +{"id": 12, "menu_summary": "Tea, pizza, wine", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg"} +{"id": 13, "menu_summary": "Fries, burgers, and shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg"} +{"id": 14, "menu_summary": "Sandwiches, salads, goldfish", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg"} +{"id": 15, "menu_summary": "Fire, scales, green dragons", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg"} +{"id": 16, "menu_summary": "Burgers, burritos, quesadillas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg"} +{"id": 17, "menu_summary": "Ice cream, slushies, and fried dough", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg"} +{"id": 18, "menu_summary": "Cookies, cereal, chicken pot pie", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg"} +{"id": 19, "menu_summary": "Convenience items, milk", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg"} +{"id": 20, "menu_summary": "Meat, salads, ice cream", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg"} +{"id": 21, "menu_summary": "Sandwiches, soups, croissants", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg"} +{"id": 22, "menu_summary": "Pollack specials, chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg"} +{"id": 23, "menu_summary": "Sandwiches, soups, sushi", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg"} +{"id": 24, "menu_summary": "Corn, vegetables, bread", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg"} +{"id": 25, "menu_summary": "A historic dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg"} +{"id": 26, "menu_summary": "The only central campus dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg"} +{"id": 27, "menu_summary": "Vegan dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg"} +{"id": 28, "menu_summary": "Sushi Fridays, freshly made pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/RPCC-Marketplace.jpg"} +{"id": 29, "menu_summary": "Taco tuesday, pizza, noodles", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg"} +{"id": 30, "menu_summary": "Coffee, tea, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg"} +{"id": 31, "menu_summary": "Falafel, rice, beans", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg"} +{"id": 32, "menu_summary": "Fries, salads, fried chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg"} {"id": 33, "name": "Terrace", "menu_summary": "Burrito and rice bowls, pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg", "location": "Statler Hall", "campus_area": "Central", "latitude": 42.446267, "longitude": -76.482314, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} {"id": 34, "name": "Mac's Caf\u00e9", "menu_summary": "Flatbreads, salads, pasta", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/macs", "location": "Statler Hotel", "campus_area": "Central", "latitude": 42.445921, "longitude": -76.481984, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} {"id": 35, "name": "Temple of Zeus", "menu_summary": "Coffee, pastries, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Zeus.jpg", "location": "Goldwin Smith Hall", "campus_area": "Central", "latitude": 42.449091, "longitude": -76.483414, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 36, "name": "Gimme Coffee", "menu_summary": "Coffee, pastries, tea", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Gimme-Coffee.jpg", "location": "Gates Hall", "campus_area": "Central", "latitude": 42.444958, "longitude": -76.481169, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} -{"id": 39, "menu_summary": "Pizza, pasta, wok, halal and kosher stations", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} +{"id": 39, "menu_summary": "Pizza, pasta, wok, halal and kosher stations", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg"} From 22a93d4cd26dadb29140a48cdaf3aeebe6e9e2fc Mon Sep 17 00:00:00 2001 From: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Date: Thu, 6 Apr 2023 17:04:24 -0400 Subject: [PATCH 137/305] Readd population in reset_db.sh --- src/reset_db.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/reset_db.sh b/src/reset_db.sh index 8c98f47..4519442 100644 --- a/src/reset_db.sh +++ b/src/reset_db.sh @@ -2,3 +2,4 @@ source ../venv/bin/activate source ../.envrc python3 manage.py makemigrations python3 manage.py migrate +python3 manage.py populate_models From d82084fd89b49d3ea2ae13dc583cfbb3cad356d3 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Fri, 7 Apr 2023 23:27:05 -0500 Subject: [PATCH 138/305] Implement google oauth --- src/eatery_blue_backend/settings.py | 6 + src/eatery_blue_backend/utils.py | 12 ++ .../controllers/authenticate_controller.py | 129 ++++++++++++++++++ .../0003_rename_netid_student_net_id.py | 18 +++ src/person/models.py | 2 +- src/person/serializers.py | 18 ++- src/person/urls.py | 3 +- src/person/views.py | 16 ++- 8 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 src/eatery_blue_backend/utils.py create mode 100644 src/person/controllers/authenticate_controller.py create mode 100644 src/person/migrations/0003_rename_netid_student_net_id.py diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 712ae70..50888d0 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -38,6 +38,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "rest_framework.authtoken", "eatery", "alert", "event", @@ -135,3 +136,8 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +# Authentication Controller +AUTH_PASSWORD_SALT = os.getenv("AUTH_PASSWORD_SALT") +ACCESS_TOKEN_AGE = 60 * 15 # 15 minutes +LOCAL_AUTH = os.getenv("LOCAL") == "True" \ No newline at end of file diff --git a/src/eatery_blue_backend/utils.py b/src/eatery_blue_backend/utils.py new file mode 100644 index 0000000..4c5809d --- /dev/null +++ b/src/eatery_blue_backend/utils.py @@ -0,0 +1,12 @@ +from rest_framework import status +from rest_framework.response import Response + + +def success_response(data="", status=status.HTTP_200_OK): + """Returns a Response with `status` (200 default) and `data` if provided.""" + return Response({"data": data}, status=status) + + +def failure_response(message="", status=status.HTTP_404_NOT_FOUND): + """Returns a Response with `status` (404 default) and `message` if provided.""" + return Response({"error": message}, status=status) \ No newline at end of file diff --git a/src/person/controllers/authenticate_controller.py b/src/person/controllers/authenticate_controller.py new file mode 100644 index 0000000..fc39835 --- /dev/null +++ b/src/person/controllers/authenticate_controller.py @@ -0,0 +1,129 @@ +from eatery_blue_backend import settings +from eatery_blue_backend.utils import failure_response +from rest_framework.response import Response +from eatery_blue_backend.utils import success_response +from django.contrib.auth import authenticate +from django.contrib.auth import login as django_login +from django.contrib.auth.models import User +from django.utils import timezone +from google.auth.transport import requests +from google.oauth2 import id_token +from person.models import Student +from rest_framework import status +from rest_framework.authtoken.models import Token + + +class AuthenticateController: + def __init__(self, request, data, serializer): + self._request = request + self._data = data + self._serializer = serializer + + def _authenticate(self, username, password): + authenticated_user = authenticate( + self._request, username=username, password=password + ) + if authenticated_user is None: + # Google User ID doesn't correspond to net_id + # Could result from debugging mistake or malicious activity... + return None, status.HTTP_403_FORBIDDEN + django_login(self._request, authenticated_user) + return authenticated_user, status.HTTP_200_OK + + def _issue_access_token(self, user): + """Returns a new access token for `user` if expired; otherwise, returns the original access token.""" + token, _ = Token.objects.get_or_create(user=user) + elapsed_seconds = (timezone.now() - token.created).total_seconds() + if settings.ACCESS_TOKEN_AGE <= elapsed_seconds: + token.delete() + token = Token.objects.create(user=user) + return token.key + + def _create_user(self, user_data): + """Returns a newly created User object from `user_data`.""" + return User.objects.create_user(**user_data) + + def _create_student(self, person_data): + """Returns a newly created Student object from `person_data`.""" + person = Student(**person_data) + person.save() + return person + + def _get_token_info(self, token): + """Returns token information if `token` is valid. If in `LOCAL` mode, returns the request data.""" + if not settings.LOCAL_AUTH: + try: + return id_token.verify_oauth2_token(token, requests.Request()) + except ValueError: + return None + return self._data + + def _prepare_token_info(self, token_info): + """Returns relevant information from `token_info`.""" + username = token_info.get("email") + google_user_id = token_info.get("sub") + password = settings.AUTH_PASSWORD_SALT + google_user_id + first_name = token_info.get("given_name") + last_name = token_info.get("family_name") + net_id = token_info.get("email").split("@")[0] + return net_id, username, password, first_name, last_name + + def process(self): + """Returns the current access token or a new one if expired. If the user isn't authenticated yet, + logs an existing user in or registers a new one.""" + user = self._request.user + status_code = status.HTTP_200_OK + if self._data.get("username") and self._data.get("password"): + user, status_code = self._authenticate( + self._data.get("username"), self._data.get("password") + ) + if user is None: + return failure_response("Bad credentials provided.", status=status_code) + elif not user.is_authenticated: + token = self._data.get("id_token") + token_info = self._get_token_info(token) + if token_info is None: + return failure_response("ID Token is not valid.", status.HTTP_401_UNAUTHORIZED) + user, status_code = self._login(token_info) + if user is None: + return failure_response("ID Token is not valid.", status=status_code) + access_token = self._issue_access_token(user) + return success_response( + self._serializer(user, context={"access_token": access_token}).data, + status=status_code, + ) + + def _login(self, token_info): + """Returns the authenticated user (if no issues) and status code depending on + whether a new user registered.""" + net_id, username, password, _, _= self._prepare_token_info(token_info) + person_exists = Student.objects.filter(net_id=net_id) + if not person_exists: + self._register(token_info) + authenticated_user, auth_status = self._authenticate(username, password) + return ( + authenticated_user, + status.HTTP_201_CREATED if not person_exists else auth_status, + ) + + def _register(self, token_info): + """Creates and associates a User and Person object.""" + ( + net_id, + username, + password, + first_name, + last_name, + ) = self._prepare_token_info(token_info) + user_data = { + "username": username, + "password": password, + "first_name": first_name, + "last_name": last_name, + } + user = self._create_user(user_data) + person_data = { + "user": user, + "net_id": net_id, + } + self._create_student(person_data) \ No newline at end of file diff --git a/src/person/migrations/0003_rename_netid_student_net_id.py b/src/person/migrations/0003_rename_netid_student_net_id.py new file mode 100644 index 0000000..a89a9dc --- /dev/null +++ b/src/person/migrations/0003_rename_netid_student_net_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2023-04-08 04:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('person', '0002_chef_user_student_favorite_eateries_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='student', + old_name='netid', + new_name='net_id', + ), + ] diff --git a/src/person/models.py b/src/person/models.py index 4542c64..9e121f5 100644 --- a/src/person/models.py +++ b/src/person/models.py @@ -3,7 +3,7 @@ class Student(models.Model): - netid = models.TextField() + net_id = models.TextField() favorite_eateries = models.ManyToManyField('eatery.Eatery', related_name='student') favorite_items = models.ManyToManyField('item.Item', related_name='student') user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) diff --git a/src/person/serializers.py b/src/person/serializers.py index b0bf8d5..f4330f3 100644 --- a/src/person/serializers.py +++ b/src/person/serializers.py @@ -1,13 +1,29 @@ from rest_framework import serializers from person.models import Student, Chef +from django.contrib.auth.models import User class StudentSerializer(serializers.ModelSerializer): class Meta: model = Student - fields = ['id', 'netid', 'user', 'favorite_eateries', 'favorite_items'] + fields = ['id', 'net_id', 'user', 'favorite_eateries', 'favorite_items'] class ChefSerializer(serializers.ModelSerializer): class Meta: model = Chef fields = ['id', 'user', 'eateries_managed'] + +class AuthenticateSerializer(serializers.ModelSerializer): + access_token = serializers.SerializerMethodField(method_name="get_access_token") + + class Meta: + model = User + fields = ( + "access_token", + User.USERNAME_FIELD, + "first_name", + "last_name", + ) + + def get_access_token(self, instance): + return self.context.get("access_token") \ No newline at end of file diff --git a/src/person/urls.py b/src/person/urls.py index cef5660..2c85b1b 100644 --- a/src/person/urls.py +++ b/src/person/urls.py @@ -7,5 +7,6 @@ router.register(r'chef', views.ChefViewSet) urlpatterns = [ - path('', include(router.urls)) + path('', include(router.urls)), + path("authenticate/", views.AuthenticateView.as_view(), name="authenticate"), ] \ No newline at end of file diff --git a/src/person/views.py b/src/person/views.py index bc04b81..c4c36bb 100644 --- a/src/person/views.py +++ b/src/person/views.py @@ -1,6 +1,9 @@ +import json from rest_framework import viewsets +from rest_framework import generics from person.models import Student, Chef -from person.serializers import StudentSerializer, ChefSerializer +from person.serializers import StudentSerializer, ChefSerializer, AuthenticateSerializer +from .controllers.authenticate_controller import AuthenticateController class StudentViewSet(viewsets.ModelViewSet): queryset = Student.objects.all() @@ -9,3 +12,14 @@ class StudentViewSet(viewsets.ModelViewSet): class ChefViewSet(viewsets.ModelViewSet): queryset = Chef.objects.all() serializer_class = ChefSerializer + +class AuthenticateView(generics.GenericAPIView): + serializer_class = AuthenticateSerializer + + def post(self, request): + """Authenticate the current user.""" + try: + data = json.loads(request.body) + except json.JSONDecodeError: + data = request.data + return AuthenticateController(request, data, self.serializer_class).process() \ No newline at end of file From a148f06b3df1bd98680b01148cc5bb9737d22a70 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 12 Apr 2023 17:50:03 -0400 Subject: [PATCH 139/305] Add migrations for reports --- src/report/migrations/0001_initial.py | 25 +++++++++++++++++++++++++ src/report/migrations/__init__.py | 0 2 files changed, 25 insertions(+) create mode 100644 src/report/migrations/0001_initial.py create mode 100644 src/report/migrations/__init__.py diff --git a/src/report/migrations/0001_initial.py b/src/report/migrations/0001_initial.py new file mode 100644 index 0000000..3bae163 --- /dev/null +++ b/src/report/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0 on 2023-04-12 21:45 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('eatery', '0003_alter_eatery_location_alter_eatery_menu_summary'), + ] + + operations = [ + migrations.CreateModel( + name='Report', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField()), + ('created', models.DateTimeField(auto_now_add=True)), + ('eatery', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='eatery.eatery')), + ], + ), + ] diff --git a/src/report/migrations/__init__.py b/src/report/migrations/__init__.py new file mode 100644 index 0000000..e69de29 From 895a7ace2c48be905eb79ad2c85f29076fd0c46e Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 12 Apr 2023 23:12:52 -0400 Subject: [PATCH 140/305] reset migrations for start of prod --- src/alert/migrations/0001_initial.py | 2 +- src/api/migrations/__init__.py | 0 src/category/migrations/0001_initial.py | 4 +- src/eatery/migrations/0001_initial.py | 6 +-- .../0002_alter_eatery_menu_summary.py | 18 -------- ...tery_location_alter_eatery_menu_summary.py | 23 ---------- src/event/controllers/populate_event.py | 4 +- src/event/migrations/0001_initial.py | 8 ++-- .../0002_alter_event_end_alter_event_start.py | 23 ---------- .../0003_alter_event_event_description.py | 18 -------- .../0004_alter_event_event_description.py | 18 -------- ...005_remove_event_end_remove_event_start.py | 21 ---------- .../migrations/0006_event_end_event_start.py | 23 ---------- src/item/migrations/0001_initial.py | 2 +- src/menu/migrations/__init__.py | 0 src/person/migrations/0001_initial.py | 15 +++++-- ...user_student_favorite_eateries_and_more.py | 42 ------------------- .../0003_rename_netid_student_net_id.py | 18 -------- src/report/migrations/0001_initial.py | 4 +- 19 files changed, 26 insertions(+), 223 deletions(-) create mode 100644 src/api/migrations/__init__.py delete mode 100644 src/eatery/migrations/0002_alter_eatery_menu_summary.py delete mode 100644 src/eatery/migrations/0003_alter_eatery_location_alter_eatery_menu_summary.py delete mode 100644 src/event/migrations/0002_alter_event_end_alter_event_start.py delete mode 100644 src/event/migrations/0003_alter_event_event_description.py delete mode 100644 src/event/migrations/0004_alter_event_event_description.py delete mode 100644 src/event/migrations/0005_remove_event_end_remove_event_start.py delete mode 100644 src/event/migrations/0006_event_end_event_start.py create mode 100644 src/menu/migrations/__init__.py delete mode 100644 src/person/migrations/0002_chef_user_student_favorite_eateries_and_more.py delete mode 100644 src/person/migrations/0003_rename_netid_student_net_id.py diff --git a/src/alert/migrations/0001_initial.py b/src/alert/migrations/0001_initial.py index d9eca2b..46b6e73 100644 --- a/src/alert/migrations/0001_initial.py +++ b/src/alert/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2022-11-14 23:56 +# Generated by Django 4.0 on 2023-04-13 00:10 from django.db import migrations, models import django.db.models.deletion diff --git a/src/api/migrations/__init__.py b/src/api/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/category/migrations/0001_initial.py b/src/category/migrations/0001_initial.py index 3935446..bdcbcb3 100644 --- a/src/category/migrations/0001_initial.py +++ b/src/category/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2023-04-05 20:49 +# Generated by Django 4.0 on 2023-04-13 00:10 from django.db import migrations, models import django.db.models.deletion @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('event', '0006_event_end_event_start'), + ('event', '0001_initial'), ] operations = [ diff --git a/src/eatery/migrations/0001_initial.py b/src/eatery/migrations/0001_initial.py index daed3c9..ca330b0 100644 --- a/src/eatery/migrations/0001_initial.py +++ b/src/eatery/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2022-11-14 23:56 +# Generated by Django 4.0 on 2023-04-13 00:10 from django.db import migrations, models @@ -16,9 +16,9 @@ class Migration(migrations.Migration): fields=[ ('id', models.IntegerField(primary_key=True, serialize=False)), ('name', models.CharField(max_length=40)), - ('menu_summary', models.CharField(blank=True, max_length=60)), + ('menu_summary', models.TextField(blank=True, default='', null=True)), ('image_url', models.URLField(blank=True)), - ('location', models.CharField(blank=True, max_length=30)), + ('location', models.TextField(blank=True)), ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), ('online_order_url', models.URLField(blank=True, null=True)), ('latitude', models.FloatField(blank=True, null=True)), diff --git a/src/eatery/migrations/0002_alter_eatery_menu_summary.py b/src/eatery/migrations/0002_alter_eatery_menu_summary.py deleted file mode 100644 index dd0e8cf..0000000 --- a/src/eatery/migrations/0002_alter_eatery_menu_summary.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0 on 2022-11-15 04:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eatery', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='eatery', - name='menu_summary', - field=models.CharField(blank=True, default='', max_length=60, null=True), - ), - ] diff --git a/src/eatery/migrations/0003_alter_eatery_location_alter_eatery_menu_summary.py b/src/eatery/migrations/0003_alter_eatery_location_alter_eatery_menu_summary.py deleted file mode 100644 index c1762cd..0000000 --- a/src/eatery/migrations/0003_alter_eatery_location_alter_eatery_menu_summary.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.0 on 2023-03-13 17:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eatery', '0002_alter_eatery_menu_summary'), - ] - - operations = [ - migrations.AlterField( - model_name='eatery', - name='location', - field=models.TextField(blank=True), - ), - migrations.AlterField( - model_name='eatery', - name='menu_summary', - field=models.TextField(blank=True, default='', null=True), - ), - ] diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py index dcad6bb..e9a5d21 100644 --- a/src/event/controllers/populate_event.py +++ b/src/event/controllers/populate_event.py @@ -25,8 +25,8 @@ def generate_events(self, json_eatery): data = { 'eatery': eatery_id, 'event_description': json_event["descr"], - 'start' : json_event["startTimestamp"], - 'end' : json_event["endTimestamp"]} + 'start' : int(json_event["startTimestamp"]), + 'end' : int(json_event["endTimestamp"])} event = EventSerializer(data=data) diff --git a/src/event/migrations/0001_initial.py b/src/event/migrations/0001_initial.py index 29ba406..fc114f2 100644 --- a/src/event/migrations/0001_initial.py +++ b/src/event/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2022-11-14 23:56 +# Generated by Django 4.0 on 2023-04-13 00:10 from django.db import migrations, models import django.db.models.deletion @@ -17,9 +17,9 @@ class Migration(migrations.Migration): name='Event', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), - ('event_description', models.CharField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', max_length=10, null=True)), - ('start', models.DateTimeField(auto_now_add=True)), - ('end', models.DateTimeField(auto_now_add=True)), + ('event_description', models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', null=True)), + ('start', models.IntegerField(default=0)), + ('end', models.IntegerField(default=0)), ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='events', to='eatery.eatery')), ], ), diff --git a/src/event/migrations/0002_alter_event_end_alter_event_start.py b/src/event/migrations/0002_alter_event_end_alter_event_start.py deleted file mode 100644 index cc44cc9..0000000 --- a/src/event/migrations/0002_alter_event_end_alter_event_start.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.0 on 2022-11-15 05:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('event', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='event', - name='end', - field=models.DateTimeField(), - ), - migrations.AlterField( - model_name='event', - name='start', - field=models.DateTimeField(), - ), - ] diff --git a/src/event/migrations/0003_alter_event_event_description.py b/src/event/migrations/0003_alter_event_event_description.py deleted file mode 100644 index 24db4b5..0000000 --- a/src/event/migrations/0003_alter_event_event_description.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0 on 2023-03-13 17:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('event', '0002_alter_event_end_alter_event_start'), - ] - - operations = [ - migrations.AlterField( - model_name='event', - name='event_description', - field=models.CharField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', max_length=30, null=True), - ), - ] diff --git a/src/event/migrations/0004_alter_event_event_description.py b/src/event/migrations/0004_alter_event_event_description.py deleted file mode 100644 index eca300c..0000000 --- a/src/event/migrations/0004_alter_event_event_description.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0 on 2023-03-13 17:49 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('event', '0003_alter_event_event_description'), - ] - - operations = [ - migrations.AlterField( - model_name='event', - name='event_description', - field=models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', null=True), - ), - ] diff --git a/src/event/migrations/0005_remove_event_end_remove_event_start.py b/src/event/migrations/0005_remove_event_end_remove_event_start.py deleted file mode 100644 index 47d2737..0000000 --- a/src/event/migrations/0005_remove_event_end_remove_event_start.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 4.0 on 2023-04-05 06:20 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('event', '0004_alter_event_event_description'), - ] - - operations = [ - migrations.RemoveField( - model_name='event', - name='end', - ), - migrations.RemoveField( - model_name='event', - name='start', - ), - ] diff --git a/src/event/migrations/0006_event_end_event_start.py b/src/event/migrations/0006_event_end_event_start.py deleted file mode 100644 index 9ed44fd..0000000 --- a/src/event/migrations/0006_event_end_event_start.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.0 on 2023-04-05 06:23 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('event', '0005_remove_event_end_remove_event_start'), - ] - - operations = [ - migrations.AddField( - model_name='event', - name='end', - field=models.IntegerField(default=0), - ), - migrations.AddField( - model_name='event', - name='start', - field=models.IntegerField(default=0), - ), - ] diff --git a/src/item/migrations/0001_initial.py b/src/item/migrations/0001_initial.py index 65f15d7..621174f 100644 --- a/src/item/migrations/0001_initial.py +++ b/src/item/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2023-04-05 20:49 +# Generated by Django 4.0 on 2023-04-13 00:10 from django.db import migrations, models import django.db.models.deletion diff --git a/src/menu/migrations/__init__.py b/src/menu/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/person/migrations/0001_initial.py b/src/person/migrations/0001_initial.py index 05d1e0c..0bbad96 100644 --- a/src/person/migrations/0001_initial.py +++ b/src/person/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 4.0 on 2023-03-31 22:21 +# Generated by Django 4.0 on 2023-04-13 00:10 from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -8,7 +9,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('eatery', '0003_alter_eatery_location_alter_eatery_menu_summary'), + ('item', '0001_initial'), + ('eatery', '0001_initial'), + ('auth', '0012_alter_user_first_name_max_length'), ] operations = [ @@ -16,14 +19,18 @@ class Migration(migrations.Migration): name='Student', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('netid', models.TextField()), + ('net_id', models.TextField()), + ('favorite_eateries', models.ManyToManyField(related_name='student', to='eatery.Eatery')), + ('favorite_items', models.ManyToManyField(related_name='student', to='item.Item')), + ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), ], ), migrations.CreateModel( name='Chef', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('eateries_managed', models.ManyToManyField(related_name='chefs', to='eatery.Eatery')), + ('eateries_managed', models.ManyToManyField(related_name='chef', to='eatery.Eatery')), + ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), ], ), ] diff --git a/src/person/migrations/0002_chef_user_student_favorite_eateries_and_more.py b/src/person/migrations/0002_chef_user_student_favorite_eateries_and_more.py deleted file mode 100644 index 1e99a13..0000000 --- a/src/person/migrations/0002_chef_user_student_favorite_eateries_and_more.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 4.0 on 2023-03-31 22:35 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('item', '0002_alter_item_base_price'), - ('eatery', '0003_alter_eatery_location_alter_eatery_menu_summary'), - ('auth', '0012_alter_user_first_name_max_length'), - ('person', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='chef', - name='user', - field=models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user'), - ), - migrations.AddField( - model_name='student', - name='favorite_eateries', - field=models.ManyToManyField(related_name='student', to='eatery.Eatery'), - ), - migrations.AddField( - model_name='student', - name='favorite_items', - field=models.ManyToManyField(related_name='student', to='item.Item'), - ), - migrations.AddField( - model_name='student', - name='user', - field=models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user'), - ), - migrations.AlterField( - model_name='chef', - name='eateries_managed', - field=models.ManyToManyField(related_name='chef', to='eatery.Eatery'), - ), - ] diff --git a/src/person/migrations/0003_rename_netid_student_net_id.py b/src/person/migrations/0003_rename_netid_student_net_id.py deleted file mode 100644 index a89a9dc..0000000 --- a/src/person/migrations/0003_rename_netid_student_net_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 4.0 on 2023-04-08 04:16 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('person', '0002_chef_user_student_favorite_eateries_and_more'), - ] - - operations = [ - migrations.RenameField( - model_name='student', - old_name='netid', - new_name='net_id', - ), - ] diff --git a/src/report/migrations/0001_initial.py b/src/report/migrations/0001_initial.py index 3bae163..7fa1323 100644 --- a/src/report/migrations/0001_initial.py +++ b/src/report/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2023-04-12 21:45 +# Generated by Django 4.0 on 2023-04-13 00:10 from django.db import migrations, models import django.db.models.deletion @@ -9,7 +9,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('eatery', '0003_alter_eatery_location_alter_eatery_menu_summary'), + ('eatery', '0001_initial'), ] operations = [ From 63db2fb87c03a3f1e9e62e57d9a8ed6d32c87572 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Thu, 13 Apr 2023 10:03:02 -0400 Subject: [PATCH 141/305] fix menu removed --- src/menu/migrations/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/menu/migrations/__init__.py diff --git a/src/menu/migrations/__init__.py b/src/menu/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 From 1b899f42bba32b3c9587a083386946b8636d81c6 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Thu, 13 Apr 2023 10:15:57 -0400 Subject: [PATCH 142/305] fix api removed --- src/api/migrations/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/api/migrations/__init__.py diff --git a/src/api/migrations/__init__.py b/src/api/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 From 68b1e2a3a757fa0afeaf1bace03a293b3974e705 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Thu, 27 Apr 2023 15:06:55 -0400 Subject: [PATCH 143/305] Implement Wait Times (#48) * create swipe model * Fix model typo * add migration file * modify migrations * Implement models for waittimes * Change env name * Implement wait time * clean up code * fix population * PR changes * Remove int_to_day function from Swipe --------- Co-authored-by: Kidus Zegeye --- .envrctemplate | 2 + .gitignore | 2 + docker-compose.server.yml | 2 +- src/cdn_parser/controllers/populate_models.py | 9 +- src/eatery/controllers/populate_eatery.py | 23 ++- src/eatery/models.py | 4 - src/eatery/serializers.py | 5 +- src/eatery/util/constants.py | 7 + src/eatery/util/eatery_store.txt | 1 + src/eatery_blue_backend/settings.py | 1 + src/event/controllers/populate_event.py | 1 - src/event/models.py | 6 + src/swipe/__init__.py | 0 src/swipe/admin.py | 3 + src/swipe/apps.py | 6 + src/swipe/controllers/populate_wait_times.py | 133 ++++++++++++++++++ src/swipe/migrations/0001_initial.py | 29 ++++ src/swipe/migrations/__init__.py | 0 src/swipe/models.py | 14 ++ src/swipe/serializers.py | 31 ++++ src/swipe/tests.py | 3 + src/swipe/views.py | 7 + 22 files changed, 272 insertions(+), 17 deletions(-) create mode 100644 src/swipe/__init__.py create mode 100644 src/swipe/admin.py create mode 100644 src/swipe/apps.py create mode 100644 src/swipe/controllers/populate_wait_times.py create mode 100644 src/swipe/migrations/0001_initial.py create mode 100644 src/swipe/migrations/__init__.py create mode 100644 src/swipe/models.py create mode 100644 src/swipe/serializers.py create mode 100644 src/swipe/tests.py create mode 100644 src/swipe/views.py diff --git a/.envrctemplate b/.envrctemplate index c1017d1..dd54878 100644 --- a/.envrctemplate +++ b/.envrctemplate @@ -7,3 +7,5 @@ export POSTGRES_USER= export POSTGRES_PASSWORD= export POSTGRES_HOST= export POSTGRES_PORT= +export VENDOR_API_KEY= +export VENDOR_BEARER_TOKEN= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 65745e8..419915a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .env eatery-dev.pem .envrc +.envlocal +.envremote # Hardcoded stuff db_snapshots/ diff --git a/docker-compose.server.yml b/docker-compose.server.yml index 74d9826..93af826 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -2,7 +2,7 @@ version: "3" services: app: - image: cornellappdev/eatery-blue-backend:${IMAGE_TAG} + image: cornellappdev/eatery-blue-dev:${IMAGE_TAG} env_file: .env stdin_open: true # docker run -i tty: true # docker run -t diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py index 1eeaefe..bca4780 100644 --- a/src/cdn_parser/controllers/populate_models.py +++ b/src/cdn_parser/controllers/populate_models.py @@ -1,10 +1,10 @@ import requests from eatery.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id from django.core import management -from eatery.models import Eatery +from event.models import Event from eatery.controllers.populate_eatery import PopulateEateryController +from swipe.controllers.populate_wait_times import PopulateWaitTimeController from event.controllers.populate_event import PopulateEventController -#from menu.controllers.populate_menu import PopulateMenuController from item.controllers.populate_item import PopulateItemController from category.controllers.populate_category import PopulateCategoryController """ @@ -51,10 +51,13 @@ def process(self): json_eateries = self.get_json() - Eatery.truncate() + Event.truncate() print("Populating eateries") PopulateEateryController().process(json_eateries) + print("Populating wait times") + PopulateWaitTimeController().process(json_eateries) + print("Populating events") events_dict = PopulateEventController().process(json_eateries) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index 1f51e6e..d3fe8e9 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -1,10 +1,8 @@ -import requests import json -from eatery.datatype.Eatery import EateryID from eatery.util.constants import dining_id_to_internal_id, SnapshotFileName from eatery.serializers import EaterySerializer from eatery.models import Eatery - +from django.core.exceptions import ObjectDoesNotExist class PopulateEateryController: def __init__(self): @@ -38,12 +36,23 @@ def generate_eatery(self, json_eatery): "location": json_eatery["location"], "online_order_url": json_eatery["onlineOrderUrl"], } + try: + object = Eatery.objects.get(id=int(eatery_id)) + except ObjectDoesNotExist: + """ + Create a new Eatery object + """ + serialized = EaterySerializer(data=data) + else: + """ + Update already-existing Eatery object + """ + serialized = EaterySerializer(object, data=data, partial=True) - eatery = EaterySerializer(data=data) - if eatery.is_valid(): - eatery.save() + if serialized.is_valid(): + serialized.save() else: - print(eatery.errors) + print(serialized.errors) def add_eatery_store(self): """ diff --git a/src/eatery/models.py b/src/eatery/models.py index 27cef9c..5845f29 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -25,7 +25,3 @@ class CampusArea(models.TextChoices): payment_accepts_brbs = models.BooleanField(null=True, blank=True) payment_accepts_cash = models.BooleanField(null=True, blank=True) - @classmethod - def truncate(cls): - with connection.cursor() as cursor: - cursor.execute('TRUNCATE TABLE {} CASCADE'.format(cls._meta.db_table)) \ No newline at end of file diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index a84973e..7c75258 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -1,6 +1,8 @@ from rest_framework import serializers from eatery.models import Eatery from event.serializers import EventSerializer, EventSerializerSimple +from swipe.serializers import WaitTimeSerializer + class EaterySerializer(serializers.ModelSerializer): @@ -17,6 +19,7 @@ class EaterySerializer(serializers.ModelSerializer): payment_accepts_brbs = serializers.BooleanField(allow_null=True) payment_accepts_cash = serializers.BooleanField(allow_null=True) + wait_time = WaitTimeSerializer(many=True, read_only=True) events = EventSerializer(many=True, read_only=True) def create(self, validated_data): @@ -25,7 +28,7 @@ def create(self, validated_data): class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'wait_time', 'events'] class EaterySerializerSimple(serializers.ModelSerializer): diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index 4db123f..c5cde42 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -3,7 +3,9 @@ from eatery.datatype.Eatery import EateryID CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" +CORNELL_VENDOR_URL = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" +DAY_OF_WEEK_LIST = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] class SnapshotFileName(Enum): EATERY_STORE = "eatery_store.txt" @@ -161,6 +163,11 @@ def vendor_name_to_internal_id(vendor_eatery_name): return EateryID.MANN_CAFE elif vendor_eatery_name == "statlermacs": return EateryID.MACS_CAFE + elif vendor_eatery_name == "morrisondining": + return EateryID.MORRISON_DINING + elif vendor_eatery_name == "novickscafe": + return EateryID.NOVICKS_CAFE + else: # TODO: Add a slack notif / flag that a wait time location was not recognized return None diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index 5445583..dc9fb4e 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -38,3 +38,4 @@ {"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 39, "menu_summary": "Pizza, pasta, wok, halal and kosher stations", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg"} +{"id": 40, "menu_summary": "Cornell Eatery"} diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index c6cb777..2687315 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -45,6 +45,7 @@ "report", "item", "category", + "swipe", "cdn_parser", "rest_framework", "person", diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py index e9a5d21..7da41b4 100644 --- a/src/event/controllers/populate_event.py +++ b/src/event/controllers/populate_event.py @@ -16,7 +16,6 @@ def generate_events(self, json_eatery): json_dates = json_eatery["operatingHours"] for json_date in json_dates: - canon_date = datetime.fromisoformat(json_date["date"]) json_events = json_date["events"] for json_event in json_events: diff --git a/src/event/models.py b/src/event/models.py index 24a9086..539f1ee 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.db import connection from eatery.models import Eatery @@ -18,3 +19,8 @@ class Event(models.Model): choices=EventDescription.choices, default = EventDescription.GENERAL, blank=True, null = True) start = models.IntegerField(default = 0) end = models.IntegerField(default = 0) + + @classmethod + def truncate(cls): + with connection.cursor() as cursor: + cursor.execute('TRUNCATE TABLE {} CASCADE'.format(cls._meta.db_table)) \ No newline at end of file diff --git a/src/swipe/__init__.py b/src/swipe/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/swipe/admin.py b/src/swipe/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/src/swipe/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/swipe/apps.py b/src/swipe/apps.py new file mode 100644 index 0000000..0781444 --- /dev/null +++ b/src/swipe/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SwipeConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'swipe' diff --git a/src/swipe/controllers/populate_wait_times.py b/src/swipe/controllers/populate_wait_times.py new file mode 100644 index 0000000..d26f41b --- /dev/null +++ b/src/swipe/controllers/populate_wait_times.py @@ -0,0 +1,133 @@ +from datetime import datetime +from swipe.serializers import WaitTimeSerializer +from swipe.models import WaitTime +from eatery.datatype.Eatery import EateryID +from eatery.util.constants import vendor_name_to_internal_id, dining_id_to_internal_id, CORNELL_VENDOR_URL, DAY_OF_WEEK_LIST +from django.core.exceptions import ObjectDoesNotExist +import requests +import os + + +class PopulateWaitTimeController(): + def __init__(self): + self = self + + def get_json(self): + try: + headers = { + 'Content-type': 'application/json', + 'x-api-key': os.environ['VENDOR_API_KEY'], + 'Authorization': os.environ['VENDOR_BEARER_TOKEN'] + } + response = requests.get(CORNELL_VENDOR_URL, headers=headers) + except Exception as e: + raise e + if response.status_code <= 400: + return response.json() + + # A serializable wait_time object + @staticmethod + def construct_wait_time(eatery_id, low_time, expected_time, high_time, day, hour, trials): + return { + 'eatery': eatery_id, + 'wait_time_low': low_time, + 'wait_time_expected': expected_time, + 'wait_time_high': high_time, + 'day': day, + 'hour': hour, + 'trials': trials + } + + # Running average of with new_value being incorporated into the old_avg, where + # old_avg had trials amount of values + @staticmethod + def running_average(old_avg, new_value, trials): + return (old_avg*trials + new_value)/(trials+1) + + # Expected amount of time (in seconds) for the length of the line to decrease by 1 person + # Returns [lower, expected, upper] + @staticmethod + def line_decrease_by_one_time(eatery_id: EateryID) -> list[int]: + if eatery_id == EateryID.MACS_CAFE: + return [24, 27, 30] + elif eatery_id == EateryID.MATTINS_CAFE: + return [9, 15, 21] + elif eatery_id == EateryID.TERRACE: + return [15, 27, 36] + elif eatery_id == EateryID.OKENSHIELDS: + return [4, 8, 12] + else: + return [18, 21, 24] + + # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the + # amount of time to check out Returns [lower, expected, upper] + @staticmethod + def base_time_to_get_food(eatery_id: EateryID) -> list[int]: + if eatery_id == EateryID.MACS_CAFE: + return [240, 300, 360] + elif eatery_id == EateryID.MATTINS_CAFE: + return [150, 210, 270] + elif eatery_id == EateryID.TERRACE: + return [60, 90, 120] + elif eatery_id == EateryID.OKENSHIELDS: + return [90, 120, 180] + else: + return [180, 240, 300] + + def process(self, json_eateries): + """ + From an swipe json from CDN, create wait_times for that eatery and add to event model. + """ + # If are populating for the first time, create wait_times for all eateries with no values + if not WaitTime.objects.all(): + for json_eatery in json_eateries: + eatery_id = int(json_eatery["id"]) + for i in range(7): + for j in range(24): + data = self.construct_wait_time(dining_id_to_internal_id(eatery_id).value,0,0,0,DAY_OF_WEEK_LIST[i], j,0) + wait_time = WaitTimeSerializer(data=data) + if wait_time.is_valid(): + wait_time.save() + else: + print(wait_time.errors) + print(json_eatery["name"]) + return + + # Iterate through all eateries in the json and add waittimes as they appear from the dining swipe json + json_swipe = self.get_json() + json_swipe_units = json_swipe["UNITS"] + unit_info = {vendor_name_to_internal_id(x["UNIT_NAME"]): x["CROWD_COUNT"] for x in json_swipe_units} + for json_eatery in json_eateries: + eatery_id = int(json_eatery["id"]) + formatted_datetime = datetime.strptime(json_swipe["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p') + day = DAY_OF_WEEK_LIST[formatted_datetime.weekday()] + hour = formatted_datetime.hour + count = unit_info.get(eatery_id, 0) + + # Calculate the expected wait time for the eatery at the given time + get_food_time = self.base_time_to_get_food(eatery_id) if count > 0 else [0, 0, 0] + low_time = count*self.line_decrease_by_one_time(eatery_id)[0] + get_food_time[0] + expected_time = count*self.line_decrease_by_one_time(eatery_id)[1] + get_food_time[1] + high_time = count*self.line_decrease_by_one_time(eatery_id)[2] + get_food_time[2] + + # Update the wait time for the eatery at the given time or create a new wait time if it doesn't exist + try: + wait_time = WaitTime.objects.get(eatery=eatery_id, day=day, hour=hour) + trials = wait_time.trials + new_low = self.running_average(wait_time.wait_time_low, low_time, trials) + new_expected = self.running_average(wait_time.wait_time_expected, expected_time, trials) + new_high = self.running_average(wait_time.wait_time_high, high_time, trials) + + data = self.construct_wait_time(eatery_id,new_low,new_expected,new_high, day, hour, wait_time.trials+1) + serialized = WaitTimeSerializer(wait_time, data=data, partial=True) + except ObjectDoesNotExist: + data = self.construct_wait_time(eatery_id,low_time,expected_time,high_time, day, hour, 1) + serialized = WaitTimeSerializer(data=data) + if serialized.is_valid(): + serialized.save() + else: + print(serialized.errors) + print(serialized) + return serialized.errors + + \ No newline at end of file diff --git a/src/swipe/migrations/0001_initial.py b/src/swipe/migrations/0001_initial.py new file mode 100644 index 0000000..33ff783 --- /dev/null +++ b/src/swipe/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 4.0 on 2023-04-22 20:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('eatery', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='WaitTime', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('wait_time_high', models.IntegerField()), + ('wait_time_expected', models.IntegerField()), + ('wait_time_low', models.IntegerField()), + ('day', models.TextField(default=0)), + ('hour', models.IntegerField(default=0)), + ('trials', models.IntegerField(default=1)), + ('eatery', models.ForeignKey(default=0, on_delete=django.db.models.deletion.DO_NOTHING, related_name='wait_time', to='eatery.eatery')), + ], + ), + ] diff --git a/src/swipe/migrations/__init__.py b/src/swipe/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/swipe/models.py b/src/swipe/models.py new file mode 100644 index 0000000..afd17db --- /dev/null +++ b/src/swipe/models.py @@ -0,0 +1,14 @@ +from django.db import models +from eatery.models import Eatery +# Create your models here. + +class WaitTime(models.Model): + id = models.AutoField(primary_key=True) + eatery = models.ForeignKey(Eatery, related_name = "wait_time", on_delete=models.DO_NOTHING, default=0) + wait_time_high = models.IntegerField() + wait_time_expected = models.IntegerField() + wait_time_low = models.IntegerField() + day = models.TextField(default=0) + hour = models.IntegerField(default=0) + trials = models.IntegerField(default = 1) + diff --git a/src/swipe/serializers.py b/src/swipe/serializers.py new file mode 100644 index 0000000..bba3819 --- /dev/null +++ b/src/swipe/serializers.py @@ -0,0 +1,31 @@ +from rest_framework import serializers +from swipe.models import WaitTime +from datetime import datetime + +class DayWaitTimeSerializer(serializers.ListSerializer): + + def to_representation(self, data): + day = WaitTime.int_to_day(datetime.now().weekday()) + data = data.filter(day=day) + return super().to_representation(data) + +class WaitTimeSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False, read_only=True) + wait_time_high = serializers.IntegerField() + wait_time_expected = serializers.IntegerField() + wait_time_low = serializers.IntegerField() + day = serializers.CharField( + allow_null=True, allow_blank=True, default=None + ) + hour = serializers.IntegerField() + trials = serializers.IntegerField() + + def create(self, validated_data): + wait_time, _ = WaitTime.objects.get_or_create(**validated_data) + return wait_time + + class Meta: + model = WaitTime + fields = ["id", "eatery", "day", "hour", "wait_time_high", "wait_time_expected", "wait_time_low", "trials"] + list_serializer_class = DayWaitTimeSerializer + \ No newline at end of file diff --git a/src/swipe/tests.py b/src/swipe/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/swipe/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/swipe/views.py b/src/swipe/views.py new file mode 100644 index 0000000..32d7823 --- /dev/null +++ b/src/swipe/views.py @@ -0,0 +1,7 @@ +from rest_framework import viewsets +from swipe.models import WaitTime +from swipe.serializers import WaitTimeSerializer + +class WaitTimeViewSet(viewsets.ModelViewSet): + queryset = WaitTime.objects.all() + serializer_class = WaitTimeSerializer \ No newline at end of file From 7ee5522141384f36f0f0f53e70c9d5ad374d1fbc Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Mon, 1 May 2023 00:41:57 -0400 Subject: [PATCH 144/305] Fix bug from last pr (#49) --- src/swipe/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/swipe/serializers.py b/src/swipe/serializers.py index bba3819..2c9ee5a 100644 --- a/src/swipe/serializers.py +++ b/src/swipe/serializers.py @@ -1,11 +1,12 @@ from rest_framework import serializers from swipe.models import WaitTime from datetime import datetime +from eatery.util.constants import DAY_OF_WEEK_LIST class DayWaitTimeSerializer(serializers.ListSerializer): def to_representation(self, data): - day = WaitTime.int_to_day(datetime.now().weekday()) + day = DAY_OF_WEEK_LIST[datetime.now().weekday()] data = data.filter(day=day) return super().to_representation(data) From 7b547ba86952d76428f857cec94547e7b372b4e8 Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 1 May 2023 19:35:10 -0400 Subject: [PATCH 145/305] bug fix --- src/swipe/controllers/populate_wait_times.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/swipe/controllers/populate_wait_times.py b/src/swipe/controllers/populate_wait_times.py index d26f41b..f906fa3 100644 --- a/src/swipe/controllers/populate_wait_times.py +++ b/src/swipe/controllers/populate_wait_times.py @@ -81,10 +81,10 @@ def process(self, json_eateries): # If are populating for the first time, create wait_times for all eateries with no values if not WaitTime.objects.all(): for json_eatery in json_eateries: - eatery_id = int(json_eatery["id"]) + eatery_id = dining_id_to_internal_id(int(json_eatery["id"])).value for i in range(7): for j in range(24): - data = self.construct_wait_time(dining_id_to_internal_id(eatery_id).value,0,0,0,DAY_OF_WEEK_LIST[i], j,0) + data = self.construct_wait_time(eatery_id,0,0,0,DAY_OF_WEEK_LIST[i], j,0) wait_time = WaitTimeSerializer(data=data) if wait_time.is_valid(): wait_time.save() @@ -98,7 +98,7 @@ def process(self, json_eateries): json_swipe_units = json_swipe["UNITS"] unit_info = {vendor_name_to_internal_id(x["UNIT_NAME"]): x["CROWD_COUNT"] for x in json_swipe_units} for json_eatery in json_eateries: - eatery_id = int(json_eatery["id"]) + eatery_id = dining_id_to_internal_id(int(json_eatery["id"])).value formatted_datetime = datetime.strptime(json_swipe["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p') day = DAY_OF_WEEK_LIST[formatted_datetime.weekday()] hour = formatted_datetime.hour From 99f072c8699c46289e4a9c07b0c0dd23a18173d0 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 2 May 2023 20:17:22 -0400 Subject: [PATCH 146/305] raise error --- src/swipe/controllers/populate_wait_times.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/swipe/controllers/populate_wait_times.py b/src/swipe/controllers/populate_wait_times.py index f906fa3..2871955 100644 --- a/src/swipe/controllers/populate_wait_times.py +++ b/src/swipe/controllers/populate_wait_times.py @@ -95,7 +95,10 @@ def process(self, json_eateries): # Iterate through all eateries in the json and add waittimes as they appear from the dining swipe json json_swipe = self.get_json() - json_swipe_units = json_swipe["UNITS"] + json_swipe_units = json_swipe.get("UNITS") + if json_swipe_units is None: + # Error in requesting vendor data + raise json_swipe unit_info = {vendor_name_to_internal_id(x["UNIT_NAME"]): x["CROWD_COUNT"] for x in json_swipe_units} for json_eatery in json_eateries: eatery_id = dining_id_to_internal_id(int(json_eatery["id"])).value From 86538071b3bf3d19a20ea686cfd9e74c89a1a2d3 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 2 May 2023 20:27:45 -0400 Subject: [PATCH 147/305] Remove raise and replace with print --- src/swipe/controllers/populate_wait_times.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/swipe/controllers/populate_wait_times.py b/src/swipe/controllers/populate_wait_times.py index 2871955..b3c6eb2 100644 --- a/src/swipe/controllers/populate_wait_times.py +++ b/src/swipe/controllers/populate_wait_times.py @@ -98,7 +98,8 @@ def process(self, json_eateries): json_swipe_units = json_swipe.get("UNITS") if json_swipe_units is None: # Error in requesting vendor data - raise json_swipe + print(json_swipe) + return None unit_info = {vendor_name_to_internal_id(x["UNIT_NAME"]): x["CROWD_COUNT"] for x in json_swipe_units} for json_eatery in json_eateries: eatery_id = dining_id_to_internal_id(int(json_eatery["id"])).value From 0447a99a34619504982e024ccd18e8ad6d14f234 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 2 May 2023 20:28:20 -0400 Subject: [PATCH 148/305] return json_swipe --- src/swipe/controllers/populate_wait_times.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/swipe/controllers/populate_wait_times.py b/src/swipe/controllers/populate_wait_times.py index b3c6eb2..dedd073 100644 --- a/src/swipe/controllers/populate_wait_times.py +++ b/src/swipe/controllers/populate_wait_times.py @@ -99,7 +99,7 @@ def process(self, json_eateries): if json_swipe_units is None: # Error in requesting vendor data print(json_swipe) - return None + return json_swipe unit_info = {vendor_name_to_internal_id(x["UNIT_NAME"]): x["CROWD_COUNT"] for x in json_swipe_units} for json_eatery in json_eateries: eatery_id = dining_id_to_internal_id(int(json_eatery["id"])).value From ee703009cc0f487e9edf072a45a608e7d583e461 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 2 May 2023 20:43:47 -0400 Subject: [PATCH 149/305] Change bearer auth header formatting --- src/swipe/controllers/populate_wait_times.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/swipe/controllers/populate_wait_times.py b/src/swipe/controllers/populate_wait_times.py index dedd073..07386e3 100644 --- a/src/swipe/controllers/populate_wait_times.py +++ b/src/swipe/controllers/populate_wait_times.py @@ -17,7 +17,7 @@ def get_json(self): headers = { 'Content-type': 'application/json', 'x-api-key': os.environ['VENDOR_API_KEY'], - 'Authorization': os.environ['VENDOR_BEARER_TOKEN'] + 'Authorization': 'Bearer '+ os.environ['VENDOR_BEARER_TOKEN'] } response = requests.get(CORNELL_VENDOR_URL, headers=headers) except Exception as e: From c88d63e8432a9690fcac5de9f7be48fce10996b0 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 16 Aug 2023 13:04:54 -0500 Subject: [PATCH 150/305] Add Vet Cafe --- src/eatery/controllers/populate_eatery.py | 1 - src/eatery/datatype/Eatery.py | 1 + src/eatery/util/constants.py | 5 +++++ src/eatery/util/eatery_store.txt | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index aea5cbd..b3de644 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -15,7 +15,6 @@ def generate_eatery(self, json_eatery): Create Eatery object from an eatery json from CornellDiningNow, and add to Eatery table. """ eatery_id = dining_id_to_internal_id(json_eatery["id"]).value - data = { "id": eatery_id, "name": json_eatery["name"], diff --git a/src/eatery/datatype/Eatery.py b/src/eatery/datatype/Eatery.py index 8117c28..b0a80bd 100644 --- a/src/eatery/datatype/Eatery.py +++ b/src/eatery/datatype/Eatery.py @@ -48,4 +48,5 @@ class EateryID(Enum): ANABELS_GROCERY = 38 MORRISON_DINING = 39 NOVICKS_CAFE = 40 + VET_CAFE = 41 diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index 4db123f..6316889 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -88,7 +88,10 @@ def dining_id_to_internal_id(id: int): return EateryID.MORRISON_DINING elif id == 44: return EateryID.NOVICKS_CAFE + elif id == 45: + return EateryID.VET_CAFE else: + print(f"Missing eatery_id {id}") return None @@ -161,6 +164,8 @@ def vendor_name_to_internal_id(vendor_eatery_name): return EateryID.MANN_CAFE elif vendor_eatery_name == "statlermacs": return EateryID.MACS_CAFE + elif vendor_eatery_name == "Vet College Cafe": + return EateryID.VET_CAFE else: # TODO: Add a slack notif / flag that a wait time location was not recognized return None diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index bde75e7..c03b845 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -36,3 +36,4 @@ {"id": 36, "name": "Gimme Coffee", "menu_summary": "Coffee, pastries, tea", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Gimme-Coffee.jpg", "location": "Gates Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.444958, "longitude": -76.481169, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} {"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "online_order_url": "", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} {"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "online_order_url": "", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true} +{"id": 45, "name": "", "menu_summary": "Freshly prepared meals, grab-and-go offerings, and coffee", "image_url": "", "location": "College of Veterinary Medicine East Campus", "campus_area": "", "online_order_url": "", "latitude": null, "longitude": null, "payment_accepts_meal_swipes": null, "payment_accepts_brbs": null, "payment_accepts_cash": null} From 43902f7f9057b9b73027579c478912a8dbd7b667 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 16 Aug 2023 13:26:08 -0500 Subject: [PATCH 151/305] Modify Eatery_Store --- src/eatery/util/eatery_store.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index f0032cf..ae4e716 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -39,4 +39,4 @@ {"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 39, "menu_summary": "Pizza, pasta, wok, halal and kosher stations", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg"} {"id": 40, "menu_summary": "Cornell Eatery"} -{"id": 45, "menu_summary": "Freshly prepared meals, grab-and-go offerings, and coffee", "image_url": "", "location": "College of Veterinary Medicine East Campus"} +{"id": 45, "menu_summary": "Freshly prepared meals, grab-and-go offerings, and coffee", "image_url": ""} From 574acbe4c37f9727fa7385209a0c0799af4c268b Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 16 Aug 2023 13:27:05 -0500 Subject: [PATCH 152/305] Modify Eatery_Store --- src/eatery/util/eatery_store.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index ae4e716..6a2b887 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -39,4 +39,4 @@ {"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 39, "menu_summary": "Pizza, pasta, wok, halal and kosher stations", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg"} {"id": 40, "menu_summary": "Cornell Eatery"} -{"id": 45, "menu_summary": "Freshly prepared meals, grab-and-go offerings, and coffee", "image_url": ""} +{"id": 41, "menu_summary": "Freshly prepared meals, grab-and-go offerings, and coffee", "image_url": ""} From 172ba876fdc8346776081de655c5998e0eb1b1b4 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 16 Aug 2023 13:35:18 -0500 Subject: [PATCH 153/305] Modify Eatery_Store --- src/eatery/util/eatery_store.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index 6a2b887..bc57a69 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -39,4 +39,4 @@ {"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 39, "menu_summary": "Pizza, pasta, wok, halal and kosher stations", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg"} {"id": 40, "menu_summary": "Cornell Eatery"} -{"id": 41, "menu_summary": "Freshly prepared meals, grab-and-go offerings, and coffee", "image_url": ""} +{"id": 41, "menu_summary": "Freshly prepared meals, grab-and-go offerings, and coffee"} From 1d2b369c37c95ad3b48c6f276b2335b18efe261f Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 27 Sep 2023 14:30:18 -0700 Subject: [PATCH 154/305] update images and descriptions (#56) --- src/eatery/util/eatery_store.txt | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index bc57a69..8440c63 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -1,42 +1,42 @@ -{"id": 1, "menu_summary": "Halal food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg"} -{"id": 2, "menu_summary": "Limes, corona, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg"} -{"id": 3, "menu_summary": "Food, drink", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg"} -{"id": 4, "menu_summary": "Quick bites and convenient food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg"} -{"id": 5, "menu_summary": "Cleanest dining hall on campus", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg"} +{"id": 1, "menu_summary": "Kosher food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg"} +{"id": 2, "menu_summary": "Noodles, sandwiches, coffee", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg"} +{"id": 3, "menu_summary": "Build your own bowl", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg"} +{"id": 4, "menu_summary": "Quick and convenient food and snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg"} +{"id": 5, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg"} {"id": 6, "menu_summary": "Sandwiches, salads, hay", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg"} {"id": 7, "menu_summary": "Bagels, bagels, bagels", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg"} -{"id": 8, "menu_summary": "Sandwiches, salads, potatoes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg"} +{"id": 8, "menu_summary": "Sandwiches, salads, drinks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg"} {"id": 9, "name": "Carol's Caf\u00e9", "menu_summary": "Carols, wine, christmas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Carols-Cafe.jpg", "location": "Balch Hall", "campus_area": "North", "latitude": 42.4533011, "longitude": -76.4791678, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": false, "online_order_url": null} -{"id": 10, "menu_summary": "Well cooked food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg"} -{"id": 11, "menu_summary": "Wraps, pizza, cookies", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg"} -{"id": 12, "menu_summary": "Tea, pizza, wine", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg"} -{"id": 13, "menu_summary": "Fries, burgers, and shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg"} +{"id": 10, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg"} +{"id": 11, "menu_summary": "Ice cream and more ice cream", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg"} +{"id": 12, "menu_summary": "Smoothies, quesadillas, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg"} +{"id": 13, "menu_summary": "Fries, burgers, and wraps", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg"} {"id": 14, "menu_summary": "Sandwiches, salads, goldfish", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg"} {"id": 15, "menu_summary": "Fire, scales, green dragons", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg"} -{"id": 16, "menu_summary": "Burgers, burritos, quesadillas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg"} +{"id": 16, "menu_summary": "Hotdogs, hotdogs, and hotdogs", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg"} {"id": 17, "menu_summary": "Ice cream, slushies, and fried dough", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg"} -{"id": 18, "menu_summary": "Cookies, cereal, chicken pot pie", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg"} -{"id": 19, "menu_summary": "Convenience items, milk", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg"} -{"id": 20, "menu_summary": "Meat, salads, ice cream", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg"} -{"id": 21, "menu_summary": "Sandwiches, soups, croissants", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg"} -{"id": 22, "menu_summary": "Pollack specials, chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg"} +{"id": 18, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg"} +{"id": 19, "menu_summary": "Hot Sandwiches and convenience items", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg"} +{"id": 20, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg"} +{"id": 21, "menu_summary": "Sandwiches, soups, coffee", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg"} +{"id": 22, "menu_summary": "Salads, wraps, coffee", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg"} {"id": 23, "menu_summary": "Sandwiches, soups, sushi", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg"} -{"id": 24, "menu_summary": "Corn, vegetables, bread", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg"} -{"id": 25, "menu_summary": "A historic dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg"} +{"id": 24, "menu_summary": "Sandwiches, salads, wraps", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg"} +{"id": 25, "menu_summary": "Freshly remodeled dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg"} {"id": 26, "menu_summary": "The only central campus dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg"} -{"id": 27, "menu_summary": "Vegan dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg"} +{"id": 27, "menu_summary": "Gluten free dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg"} {"id": 28, "menu_summary": "Sushi Fridays, freshly made pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/RPCC-Marketplace.jpg"} -{"id": 29, "menu_summary": "Taco tuesday, pizza, noodles", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg"} +{"id": 29, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg"} {"id": 30, "menu_summary": "Coffee, tea, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg"} -{"id": 31, "menu_summary": "Falafel, rice, beans", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg"} -{"id": 32, "menu_summary": "Fries, salads, fried chicken", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg"} +{"id": 31, "menu_summary": "Soups, salads, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg"} +{"id": 32, "menu_summary": "Burgers, pasta, quesadillas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg"} {"id": 33, "name": "Terrace", "menu_summary": "Burrito and rice bowls, pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg", "location": "Statler Hall", "campus_area": "Central", "latitude": 42.446267, "longitude": -76.482314, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} -{"id": 34, "name": "Mac's Caf\u00e9", "menu_summary": "Flatbreads, salads, pasta", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/macs", "location": "Statler Hotel", "campus_area": "Central", "latitude": 42.445921, "longitude": -76.481984, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} +{"id": 34, "name": "Mac's Caf\u00e9", "menu_summary": "Flatbreads, pasta, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/macs", "location": "Statler Hotel", "campus_area": "Central", "latitude": 42.445921, "longitude": -76.481984, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} {"id": 35, "name": "Temple of Zeus", "menu_summary": "Coffee, pastries, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Zeus.jpg", "location": "Goldwin Smith Hall", "campus_area": "Central", "latitude": 42.449091, "longitude": -76.483414, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 36, "name": "Gimme Coffee", "menu_summary": "Coffee, pastries, tea", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Gimme-Coffee.jpg", "location": "Gates Hall", "campus_area": "Central", "latitude": 42.444958, "longitude": -76.481169, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} -{"id": 39, "menu_summary": "Pizza, pasta, wok, halal and kosher stations", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg"} -{"id": 40, "menu_summary": "Cornell Eatery"} -{"id": 41, "menu_summary": "Freshly prepared meals, grab-and-go offerings, and coffee"} +{"id": 39, "menu_summary": "Pizza, pasta, wok, halal, kosher", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg"} +{"id": 40, "menu_summary": "Baked Goods, Coffee, Sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/novicks-cafe.jpg"} +{"id": 41, "menu_summary": "Grab-n-Go, American, kosher", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/vets-cafe.jpg"} From 62fd8c127ec8c70c478854b829d67230deb95d94 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 27 Sep 2023 14:30:29 -0700 Subject: [PATCH 155/305] fix wait times (#57) --- src/eatery/util/constants.py | 2 +- src/swipe/controllers/populate_wait_times.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index 9d13fb3..a4c4af4 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -166,7 +166,7 @@ def vendor_name_to_internal_id(vendor_eatery_name): return EateryID.MANN_CAFE elif vendor_eatery_name == "statlermacs": return EateryID.MACS_CAFE - elif vendor_eatery_name == "morrisondining": + elif vendor_eatery_name == "morrisondining" or vendor_eatery_name == "morrison": return EateryID.MORRISON_DINING elif vendor_eatery_name == "novickscafe": return EateryID.NOVICKS_CAFE diff --git a/src/swipe/controllers/populate_wait_times.py b/src/swipe/controllers/populate_wait_times.py index 07386e3..9ef1dd8 100644 --- a/src/swipe/controllers/populate_wait_times.py +++ b/src/swipe/controllers/populate_wait_times.py @@ -17,12 +17,12 @@ def get_json(self): headers = { 'Content-type': 'application/json', 'x-api-key': os.environ['VENDOR_API_KEY'], - 'Authorization': 'Bearer '+ os.environ['VENDOR_BEARER_TOKEN'] + 'Authorization': os.environ['VENDOR_BEARER_TOKEN'] } response = requests.get(CORNELL_VENDOR_URL, headers=headers) except Exception as e: raise e - if response.status_code <= 400: + if response.status_code < 400: return response.json() # A serializable wait_time object @@ -42,7 +42,7 @@ def construct_wait_time(eatery_id, low_time, expected_time, high_time, day, hour # old_avg had trials amount of values @staticmethod def running_average(old_avg, new_value, trials): - return (old_avg*trials + new_value)/(trials+1) + return int((old_avg*trials + new_value)/(trials+1)) # Expected amount of time (in seconds) for the length of the line to decrease by 1 person # Returns [lower, expected, upper] @@ -95,18 +95,20 @@ def process(self, json_eateries): # Iterate through all eateries in the json and add waittimes as they appear from the dining swipe json json_swipe = self.get_json() + if not json_swipe: + return json_swipe_units = json_swipe.get("UNITS") if json_swipe_units is None: # Error in requesting vendor data print(json_swipe) return json_swipe - unit_info = {vendor_name_to_internal_id(x["UNIT_NAME"]): x["CROWD_COUNT"] for x in json_swipe_units} + unit_info = {vendor_name_to_internal_id(x["UNIT_NAME"]).value: x["CROWD_COUNT"] for x in json_swipe_units} for json_eatery in json_eateries: eatery_id = dining_id_to_internal_id(int(json_eatery["id"])).value formatted_datetime = datetime.strptime(json_swipe["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p') day = DAY_OF_WEEK_LIST[formatted_datetime.weekday()] hour = formatted_datetime.hour - count = unit_info.get(eatery_id, 0) + count = int(unit_info.get(eatery_id, 0)) # Calculate the expected wait time for the eatery at the given time get_food_time = self.base_time_to_get_food(eatery_id) if count > 0 else [0, 0, 0] From aaf57746ef269d6899330d8bfa64cfa7872d8f26 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 27 Sep 2023 14:30:59 -0700 Subject: [PATCH 156/305] remove unused user code (#58) --- src/eatery_blue_backend/utils.py | 12 -- .../controllers/authenticate_controller.py | 129 ------------------ src/person/views.py | 18 +-- 3 files changed, 2 insertions(+), 157 deletions(-) delete mode 100644 src/eatery_blue_backend/utils.py delete mode 100644 src/person/controllers/authenticate_controller.py diff --git a/src/eatery_blue_backend/utils.py b/src/eatery_blue_backend/utils.py deleted file mode 100644 index 4c5809d..0000000 --- a/src/eatery_blue_backend/utils.py +++ /dev/null @@ -1,12 +0,0 @@ -from rest_framework import status -from rest_framework.response import Response - - -def success_response(data="", status=status.HTTP_200_OK): - """Returns a Response with `status` (200 default) and `data` if provided.""" - return Response({"data": data}, status=status) - - -def failure_response(message="", status=status.HTTP_404_NOT_FOUND): - """Returns a Response with `status` (404 default) and `message` if provided.""" - return Response({"error": message}, status=status) \ No newline at end of file diff --git a/src/person/controllers/authenticate_controller.py b/src/person/controllers/authenticate_controller.py deleted file mode 100644 index fc39835..0000000 --- a/src/person/controllers/authenticate_controller.py +++ /dev/null @@ -1,129 +0,0 @@ -from eatery_blue_backend import settings -from eatery_blue_backend.utils import failure_response -from rest_framework.response import Response -from eatery_blue_backend.utils import success_response -from django.contrib.auth import authenticate -from django.contrib.auth import login as django_login -from django.contrib.auth.models import User -from django.utils import timezone -from google.auth.transport import requests -from google.oauth2 import id_token -from person.models import Student -from rest_framework import status -from rest_framework.authtoken.models import Token - - -class AuthenticateController: - def __init__(self, request, data, serializer): - self._request = request - self._data = data - self._serializer = serializer - - def _authenticate(self, username, password): - authenticated_user = authenticate( - self._request, username=username, password=password - ) - if authenticated_user is None: - # Google User ID doesn't correspond to net_id - # Could result from debugging mistake or malicious activity... - return None, status.HTTP_403_FORBIDDEN - django_login(self._request, authenticated_user) - return authenticated_user, status.HTTP_200_OK - - def _issue_access_token(self, user): - """Returns a new access token for `user` if expired; otherwise, returns the original access token.""" - token, _ = Token.objects.get_or_create(user=user) - elapsed_seconds = (timezone.now() - token.created).total_seconds() - if settings.ACCESS_TOKEN_AGE <= elapsed_seconds: - token.delete() - token = Token.objects.create(user=user) - return token.key - - def _create_user(self, user_data): - """Returns a newly created User object from `user_data`.""" - return User.objects.create_user(**user_data) - - def _create_student(self, person_data): - """Returns a newly created Student object from `person_data`.""" - person = Student(**person_data) - person.save() - return person - - def _get_token_info(self, token): - """Returns token information if `token` is valid. If in `LOCAL` mode, returns the request data.""" - if not settings.LOCAL_AUTH: - try: - return id_token.verify_oauth2_token(token, requests.Request()) - except ValueError: - return None - return self._data - - def _prepare_token_info(self, token_info): - """Returns relevant information from `token_info`.""" - username = token_info.get("email") - google_user_id = token_info.get("sub") - password = settings.AUTH_PASSWORD_SALT + google_user_id - first_name = token_info.get("given_name") - last_name = token_info.get("family_name") - net_id = token_info.get("email").split("@")[0] - return net_id, username, password, first_name, last_name - - def process(self): - """Returns the current access token or a new one if expired. If the user isn't authenticated yet, - logs an existing user in or registers a new one.""" - user = self._request.user - status_code = status.HTTP_200_OK - if self._data.get("username") and self._data.get("password"): - user, status_code = self._authenticate( - self._data.get("username"), self._data.get("password") - ) - if user is None: - return failure_response("Bad credentials provided.", status=status_code) - elif not user.is_authenticated: - token = self._data.get("id_token") - token_info = self._get_token_info(token) - if token_info is None: - return failure_response("ID Token is not valid.", status.HTTP_401_UNAUTHORIZED) - user, status_code = self._login(token_info) - if user is None: - return failure_response("ID Token is not valid.", status=status_code) - access_token = self._issue_access_token(user) - return success_response( - self._serializer(user, context={"access_token": access_token}).data, - status=status_code, - ) - - def _login(self, token_info): - """Returns the authenticated user (if no issues) and status code depending on - whether a new user registered.""" - net_id, username, password, _, _= self._prepare_token_info(token_info) - person_exists = Student.objects.filter(net_id=net_id) - if not person_exists: - self._register(token_info) - authenticated_user, auth_status = self._authenticate(username, password) - return ( - authenticated_user, - status.HTTP_201_CREATED if not person_exists else auth_status, - ) - - def _register(self, token_info): - """Creates and associates a User and Person object.""" - ( - net_id, - username, - password, - first_name, - last_name, - ) = self._prepare_token_info(token_info) - user_data = { - "username": username, - "password": password, - "first_name": first_name, - "last_name": last_name, - } - user = self._create_user(user_data) - person_data = { - "user": user, - "net_id": net_id, - } - self._create_student(person_data) \ No newline at end of file diff --git a/src/person/views.py b/src/person/views.py index c4c36bb..778d6e7 100644 --- a/src/person/views.py +++ b/src/person/views.py @@ -1,9 +1,6 @@ -import json from rest_framework import viewsets -from rest_framework import generics from person.models import Student, Chef -from person.serializers import StudentSerializer, ChefSerializer, AuthenticateSerializer -from .controllers.authenticate_controller import AuthenticateController +from person.serializers import StudentSerializer, ChefSerializer class StudentViewSet(viewsets.ModelViewSet): queryset = Student.objects.all() @@ -11,15 +8,4 @@ class StudentViewSet(viewsets.ModelViewSet): class ChefViewSet(viewsets.ModelViewSet): queryset = Chef.objects.all() - serializer_class = ChefSerializer - -class AuthenticateView(generics.GenericAPIView): - serializer_class = AuthenticateSerializer - - def post(self, request): - """Authenticate the current user.""" - try: - data = json.loads(request.body) - except json.JSONDecodeError: - data = request.data - return AuthenticateController(request, data, self.serializer_class).process() \ No newline at end of file + serializer_class = ChefSerializer \ No newline at end of file From 3fd7ae92c72aecf45c32af88c11b4e27daa4e3d3 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 27 Sep 2023 14:31:10 -0700 Subject: [PATCH 157/305] remove old code (#59) --- src/alert/controllers/api/__init__.py | 0 .../api/controllers/create_transaction.py | 42 ----- .../controllers/delete_all_transactions.py | 8 - src/alert/controllers/api/dfg/main.py | 45 ----- .../api/dfg/nodes/CornellDiningNow.py | 52 ------ .../controllers/api/dfg/nodes/DfgNode.py | 10 -- .../api/dfg/nodes/EateriesFromDB.py | 49 ------ .../controllers/api/dfg/nodes/EateryStubs.py | 10 -- .../controllers/api/dfg/nodes/__init__.py | 0 .../api/dfg/nodes/macros/EateryEvents.py | 38 ---- .../api/dfg/nodes/macros/LeftMergeEateries.py | 35 ---- .../api/dfg/nodes/macros/LeftMergeEvents.py | 29 --- .../nodes/macros/LeftMergeRegularEvents.py | 11 -- .../nodes/macros/LeftMergeRepeatedEvents.py | 12 -- .../api/dfg/nodes/system/ConvertFromJson.py | 28 --- .../api/dfg/nodes/system/ConvertToJson.py | 31 ---- .../dfg/nodes/system/DictResponseWrapper.py | 20 --- .../api/dfg/nodes/system/EateryGenerator.py | 30 ---- .../api/dfg/nodes/system/InMemoryCache.py | 77 -------- .../api/dfg/nodes/system/LeftMerge.py | 58 ------ .../api/dfg/nodes/system/Mapping.py | 21 --- .../dfg/nodes/wait_times/WaitTimeFilter.py | 39 ---- .../api/dfg/nodes/wait_times/WaitTimes.py | 135 -------------- .../management/commands/export_db_snapshot.py | 107 ----------- .../management/commands/ingest_db_snapshot.py | 63 ------- .../commands/ingest_log_transactions.py | 34 ---- .../commands/ingest_recent_transactions.py | 26 --- .../api/migrations/0001_initial.py | 166 ------------------ ...ventschedule_scheduleexception_and_more.py | 111 ------------ .../controllers/api/migrations/__init__.py | 0 .../api/models/TransactionModel.py | 15 -- src/alert/controllers/api/models/__init__.py | 9 - src/alert/controllers/api/serializers.py | 45 ----- src/alert/controllers/misc_code.py | 27 --- 34 files changed, 1383 deletions(-) delete mode 100644 src/alert/controllers/api/__init__.py delete mode 100644 src/alert/controllers/api/controllers/create_transaction.py delete mode 100644 src/alert/controllers/api/controllers/delete_all_transactions.py delete mode 100644 src/alert/controllers/api/dfg/main.py delete mode 100644 src/alert/controllers/api/dfg/nodes/CornellDiningNow.py delete mode 100644 src/alert/controllers/api/dfg/nodes/DfgNode.py delete mode 100644 src/alert/controllers/api/dfg/nodes/EateriesFromDB.py delete mode 100644 src/alert/controllers/api/dfg/nodes/EateryStubs.py delete mode 100644 src/alert/controllers/api/dfg/nodes/__init__.py delete mode 100644 src/alert/controllers/api/dfg/nodes/macros/EateryEvents.py delete mode 100644 src/alert/controllers/api/dfg/nodes/macros/LeftMergeEateries.py delete mode 100644 src/alert/controllers/api/dfg/nodes/macros/LeftMergeEvents.py delete mode 100644 src/alert/controllers/api/dfg/nodes/macros/LeftMergeRegularEvents.py delete mode 100644 src/alert/controllers/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py delete mode 100644 src/alert/controllers/api/dfg/nodes/system/ConvertFromJson.py delete mode 100644 src/alert/controllers/api/dfg/nodes/system/ConvertToJson.py delete mode 100644 src/alert/controllers/api/dfg/nodes/system/DictResponseWrapper.py delete mode 100644 src/alert/controllers/api/dfg/nodes/system/EateryGenerator.py delete mode 100644 src/alert/controllers/api/dfg/nodes/system/InMemoryCache.py delete mode 100644 src/alert/controllers/api/dfg/nodes/system/LeftMerge.py delete mode 100644 src/alert/controllers/api/dfg/nodes/system/Mapping.py delete mode 100644 src/alert/controllers/api/dfg/nodes/wait_times/WaitTimeFilter.py delete mode 100644 src/alert/controllers/api/dfg/nodes/wait_times/WaitTimes.py delete mode 100644 src/alert/controllers/api/management/commands/export_db_snapshot.py delete mode 100644 src/alert/controllers/api/management/commands/ingest_db_snapshot.py delete mode 100644 src/alert/controllers/api/management/commands/ingest_log_transactions.py delete mode 100644 src/alert/controllers/api/management/commands/ingest_recent_transactions.py delete mode 100644 src/alert/controllers/api/migrations/0001_initial.py delete mode 100644 src/alert/controllers/api/migrations/0002_repeatingeventschedule_scheduleexception_and_more.py delete mode 100644 src/alert/controllers/api/migrations/__init__.py delete mode 100644 src/alert/controllers/api/models/TransactionModel.py delete mode 100644 src/alert/controllers/api/models/__init__.py delete mode 100644 src/alert/controllers/api/serializers.py delete mode 100644 src/alert/controllers/misc_code.py diff --git a/src/alert/controllers/api/__init__.py b/src/alert/controllers/api/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/alert/controllers/api/controllers/create_transaction.py b/src/alert/controllers/api/controllers/create_transaction.py deleted file mode 100644 index 86cba00..0000000 --- a/src/alert/controllers/api/controllers/create_transaction.py +++ /dev/null @@ -1,42 +0,0 @@ -from datetime import datetime, timedelta -import pytz - -from api.models import TransactionHistoryStore -from api.util.constants import vendor_name_to_internal_id - - -class CreateTransactionController: - - def __init__(self, data): - self._data = data - - def process(self): - if self._data["TIMESTAMP"] == "Invalid date": - return 0 - tz = pytz.timezone('America/New_York') - recent_datetime = tz.localize(datetime.strptime( - self._data["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p')) - canonical_date = recent_datetime.date() - block_end_time = recent_datetime.time() - if recent_datetime.hour < 4: - # between 12am and 4am associate this transaction with the previous day - canonical_date = canonical_date - timedelta(days=1) - num_inserted = 0 - ignored_names = set() - for place in self._data["UNITS"]: - internal_id = vendor_name_to_internal_id(place["UNIT_NAME"]).value - if internal_id == None: - ignored_names.add(place["UNIT_NAME"]) - else: - num_inserted += 1 - try: - TransactionHistoryStore.objects.create( - eatery_id=internal_id, - canonical_date=canonical_date, - block_end_time=block_end_time, - transaction_count=place["CROWD_COUNT"] - ) - except Exception as e: - # print(e) - num_inserted -= 1 - return num_inserted diff --git a/src/alert/controllers/api/controllers/delete_all_transactions.py b/src/alert/controllers/api/controllers/delete_all_transactions.py deleted file mode 100644 index 58b6035..0000000 --- a/src/alert/controllers/api/controllers/delete_all_transactions.py +++ /dev/null @@ -1,8 +0,0 @@ - -from api.models import TransactionHistoryStore - -class DeleteAllTransactionsController: - - def process(self): - return TransactionHistoryStore.objects.all().delete()[0] - \ No newline at end of file diff --git a/src/alert/controllers/api/dfg/main.py b/src/alert/controllers/api/dfg/main.py deleted file mode 100644 index c5be9f9..0000000 --- a/src/alert/controllers/api/dfg/main.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Dict -from api.dfg.nodes.CornellDiningNow import CornellDiningNow -from api.dfg.nodes.EateriesFromDB import EateriesFromDB -from api.dfg.nodes.EateryStubs import EateryStubs -from api.dfg.nodes.macros.EateryEvents import EateryEvents -from api.dfg.nodes.macros.LeftMergeEateries import LeftMergeEateries -from api.dfg.nodes.system.ConvertToJson import ConvertToJson -from api.dfg.nodes.system.DictResponseWrapper import DictResponseWrapper -from api.dfg.nodes.system.EateryGenerator import EateryGenerator -from api.dfg.nodes.system.InMemoryCache import InMemoryCache -from api.dfg.nodes.system.Mapping import Mapping -from api.dfg.nodes.wait_times.WaitTimeFilter import WaitTimeFilter -from api.dfg.nodes.wait_times.WaitTimes import WaitTimes - -main_dfg = DictResponseWrapper( - ConvertToJson( - InMemoryCache( - WaitTimeFilter( - LeftMergeEateries( - Mapping( - child=EateryStubs(), - fn=lambda eatery, cache: EateryGenerator( - eatery_id=eatery.id, - wait_times_dfg=WaitTimes(eatery.id, cache), - ), - ), - LeftMergeEateries( - Mapping( - child=EateryStubs(), - fn=lambda eatery, cache: EateryGenerator( - eatery_id=eatery.id, - events_dfg=EateryEvents(eatery.id, cache), - ), - ), - LeftMergeEateries( - EateriesFromDB(), - LeftMergeEateries(CornellDiningNow(), EateryStubs()), - ), - ), - ) - ) - ) - ), - re_raise_exceptions=True, -) diff --git a/src/alert/controllers/api/dfg/nodes/CornellDiningNow.py b/src/alert/controllers/api/dfg/nodes/CornellDiningNow.py deleted file mode 100644 index 98693c7..0000000 --- a/src/alert/controllers/api/dfg/nodes/CornellDiningNow.py +++ /dev/null @@ -1,52 +0,0 @@ -import requests -from api.dfg.nodes.DfgNode import DfgNode -from api.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id -from eatery.datatype.Eatery import Eatery - - -class CornellDiningNow(DfgNode): - def __call__(self, *args, **kwargs) -> list[Eatery]: - try: - response = requests.get(CORNELL_DINING_URL).json() - - except Exception as e: - raise e - - if response["status"] == "success": - json_eateries = response["data"]["eateries"] - eateries = [] - for json_eatery in json_eateries: - eateries.append(CornellDiningNow.parse_eatery(json_eatery)) - return eateries - - else: - raise Exception(response["message"]) - - @staticmethod - def parse_eatery(json_eatery: dict) -> Eatery: - # Events are parsed later - return Eatery( - id=dining_id_to_internal_id(json_eatery["id"]), - name=json_eatery["name"], - campus_area=json_eatery["campusArea"]["descrshort"], - latitude=json_eatery["latitude"], - longitude=json_eatery["longitude"], - payment_accepts_cash=True, - payment_accepts_brbs=any( - [ - method["descrshort"] == "Meal Plan - Debit" - for method in json_eatery["payMethods"] - ] - ), - payment_accepts_meal_swipes=any( - [ - method["descrshort"] == "Meal Plan - Swipe" - for method in json_eatery["payMethods"] - ] - ), - location=json_eatery["location"], - online_order_url=json_eatery["onlineOrderUrl"], - ) - - def description(self): - return "CornellDiningNow" diff --git a/src/alert/controllers/api/dfg/nodes/DfgNode.py b/src/alert/controllers/api/dfg/nodes/DfgNode.py deleted file mode 100644 index 57d5821..0000000 --- a/src/alert/controllers/api/dfg/nodes/DfgNode.py +++ /dev/null @@ -1,10 +0,0 @@ -class DfgNode: - - def __call__(self, *args, **kwargs): - raise Exception() - - def children(self): - return [] - - def description(self): - raise Exception() diff --git a/src/alert/controllers/api/dfg/nodes/EateriesFromDB.py b/src/alert/controllers/api/dfg/nodes/EateriesFromDB.py deleted file mode 100644 index 96069fb..0000000 --- a/src/alert/controllers/api/dfg/nodes/EateriesFromDB.py +++ /dev/null @@ -1,49 +0,0 @@ -from datetime import datetime - -from api.dfg.nodes.DfgNode import DfgNode -from eatery.datatype.Eatery import Eatery, EateryID -from eatery.models import EateryStore -from eatery.serializers import EateryStoreSerializer - -# eventually need to deprecate this for a custom DB backend storing all of the overrides - - -class EateriesFromDB(DfgNode): - def __call__(self, *args, **kwargs) -> list[Eatery]: - eateries = EateryStore.objects.all() - serialized_eateries = EateryStoreSerializer(data=eateries, many=True) - serialized_eateries.is_valid() - return list(serialized_eateries.data) - - - @staticmethod - def none_repr(str): - return None if str == None or len(str) == 0 else str - - @staticmethod - def eatery_from_serialized( - serialized_eatery: dict, serialized_alerts: list[dict] - ) -> Eatery: - return Eatery( - id=EateryID(serialized_eatery["id"]), - name=EateriesFromDB.none_repr(serialized_eatery["name"]), - image_url=EateriesFromDB.none_repr(serialized_eatery["image_url"]), - menu_summary=EateriesFromDB.none_repr(serialized_eatery["menu_summary"]), - campus_area=EateriesFromDB.none_repr(serialized_eatery["campus_area"]), - events=None, - latitude=serialized_eatery["latitude"], - longitude=serialized_eatery["longitude"], - payment_accepts_cash=serialized_eatery["payment_accepts_cash"], - payment_accepts_brbs=serialized_eatery["payment_accepts_brbs"], - payment_accepts_meal_swipes=serialized_eatery[ - "payment_accepts_meal_swipes" - ], - location=EateriesFromDB.none_repr(serialized_eatery["location"]), - online_order_url=EateriesFromDB.none_repr( - serialized_eatery["online_order_url"] - ), - - ) - - def description(self): - return "EateriesFromDB" diff --git a/src/alert/controllers/api/dfg/nodes/EateryStubs.py b/src/alert/controllers/api/dfg/nodes/EateryStubs.py deleted file mode 100644 index 780f15c..0000000 --- a/src/alert/controllers/api/dfg/nodes/EateryStubs.py +++ /dev/null @@ -1,10 +0,0 @@ -from api.dfg.nodes.DfgNode import DfgNode -from eatery.datatype.Eatery import Eatery, EateryID - - -class EateryStubs(DfgNode): - def __call__(self, *args, **kwargs) -> list[Eatery]: - return [Eatery(id=id) for id in EateryID] - - def description(self): - return "EateryStubs" diff --git a/src/alert/controllers/api/dfg/nodes/__init__.py b/src/alert/controllers/api/dfg/nodes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/alert/controllers/api/dfg/nodes/macros/EateryEvents.py b/src/alert/controllers/api/dfg/nodes/macros/EateryEvents.py deleted file mode 100644 index 38d55a8..0000000 --- a/src/alert/controllers/api/dfg/nodes/macros/EateryEvents.py +++ /dev/null @@ -1,38 +0,0 @@ -from api.dfg.nodes.DfgNode import DfgNode -from api.dfg.nodes.macros.LeftMergeRegularEvents import LeftMergeRegularEvents -from api.dfg.nodes.macros.LeftMergeRepeatedEvents import LeftMergeRepeatedEvents -from api.dfg.nodes.schedule.CacheMenuInjection import CacheMenuInjection -from event.controllers.ClosedSchedule import ClosedSchedule -from api.dfg.nodes.schedule.CornellDiningEvents import CornellDiningEvents -from api.dfg.nodes.schedule.ModifiedSchedules import ModifiedSchedules -from api.dfg.nodes.schedule.RepeatingSchedule import RepeatingSchedule -from eatery.datatype.Eatery import EateryID - - -# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if -# conflict) -class EateryEvents(DfgNode): - def __init__(self, eatery_id: EateryID, cache): - self.macro = CacheMenuInjection( - ClosedSchedule( - eatery_id, - LeftMergeRepeatedEvents( - ModifiedSchedules(eatery_id, cache), - LeftMergeRegularEvents( - RepeatingSchedule(eatery_id, cache), - CornellDiningEvents(eatery_id, cache), - ), - ), - cache, - ), - cache, - ) - - def children(self): - return self.macro.children() - - def __call__(self, *args, **kwargs): - return self.macro(*args, **kwargs) - - def description(self): - return "EateryEvents" diff --git a/src/alert/controllers/api/dfg/nodes/macros/LeftMergeEateries.py b/src/alert/controllers/api/dfg/nodes/macros/LeftMergeEateries.py deleted file mode 100644 index cc621d2..0000000 --- a/src/alert/controllers/api/dfg/nodes/macros/LeftMergeEateries.py +++ /dev/null @@ -1,35 +0,0 @@ -from api.dfg.nodes.DfgNode import DfgNode -from api.dfg.nodes.system.ConvertToJson import ConvertToJson -from api.dfg.nodes.system.ConvertFromJson import EateryFromJson -from api.dfg.nodes.system.LeftMerge import LeftMerge - -class LeftMergeEateries(DfgNode): - - def __init__(self, left: DfgNode, right: DfgNode): - def comparator(left, right): - if left["id"] == right["id"]: - return 0 - elif left["id"] == None: - return -1 - elif right["id"] == None: - return 1 - elif left["id"] < right["id"]: - return -1 - else: - return 1 - self.macro = EateryFromJson( - LeftMerge( - ConvertToJson(left), - ConvertToJson(right), - comparator - ) - ) - - def children(self): - return self.macro.children() - - def __call__(self, *args, **kwargs): - return self.macro(*args, **kwargs) - - def description(self): - return "LeftMergeEateries" diff --git a/src/alert/controllers/api/dfg/nodes/macros/LeftMergeEvents.py b/src/alert/controllers/api/dfg/nodes/macros/LeftMergeEvents.py deleted file mode 100644 index 19eb6cc..0000000 --- a/src/alert/controllers/api/dfg/nodes/macros/LeftMergeEvents.py +++ /dev/null @@ -1,29 +0,0 @@ -"""from api.dfg.nodes.DfgNode import DfgNode -from api.dfg.nodes.system.ConvertFromJson import EventFromJson -from api.dfg.nodes.system.ConvertToJson import ConvertToJson -from api.dfg.nodes.system.LeftMerge import LeftMerge - - -class LeftMergeEvents(DfgNode): - def __init__(self, left: DfgNode, right: DfgNode, attr_lst: list[str]): - def comparator(left, right): - left_val = [left.get(attr) for attr in attr_lst] - right_val = [right.get(attr) for attr in attr_lst] - if left_val == right_val: - return 0 - else: - return 1 - - self.macro = EventFromJson( - LeftMerge(ConvertToJson(left), ConvertToJson(right), comparator) - ) - - def children(self): - return self.macro.children() - - def __call__(self, *args, **kwargs): - return self.macro(*args, **kwargs) - - def description(self): - return "LeftMergeEvents" -""" \ No newline at end of file diff --git a/src/alert/controllers/api/dfg/nodes/macros/LeftMergeRegularEvents.py b/src/alert/controllers/api/dfg/nodes/macros/LeftMergeRegularEvents.py deleted file mode 100644 index 712187c..0000000 --- a/src/alert/controllers/api/dfg/nodes/macros/LeftMergeRegularEvents.py +++ /dev/null @@ -1,11 +0,0 @@ -from api.dfg.nodes.DfgNode import DfgNode -from api.dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents - - -class LeftMergeRegularEvents(LeftMergeEvents): - """ - Merges two lists of Event objects, regardless of how they were generated - """ - - def __init__(self, left: DfgNode, right: DfgNode): - super().__init__(left, right, ["canonical_date", "description"]) diff --git a/src/alert/controllers/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py b/src/alert/controllers/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py deleted file mode 100644 index 014da8f..0000000 --- a/src/alert/controllers/api/dfg/nodes/macros/LeftMergeRepeatedEvents.py +++ /dev/null @@ -1,12 +0,0 @@ -from api.dfg.nodes.DfgNode import DfgNode -from api.dfg.nodes.macros.LeftMergeEvents import LeftMergeEvents - - -class LeftMergeRepeatedEvents(LeftMergeEvents): - """ - Merges two lists of Event objects, one of which consists of a list of events generated by a - repeated schedule and the other of which consists of a list of events that are exceptions - """ - - def __init__(self, left: DfgNode, right: DfgNode): - super().__init__(left, right, ["canonical_date", "generated_by"]) diff --git a/src/alert/controllers/api/dfg/nodes/system/ConvertFromJson.py b/src/alert/controllers/api/dfg/nodes/system/ConvertFromJson.py deleted file mode 100644 index b440739..0000000 --- a/src/alert/controllers/api/dfg/nodes/system/ConvertFromJson.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Union - - -from api.dfg.nodes.DfgNode import DfgNode -from eatery.datatype.Eatery import Eatery - - -class EateryFromJson(DfgNode): - def __init__(self, child: DfgNode): - self.child = child - - def __call__(self, *args, **kwargs): - result = self.child(*args, **kwargs) - return EateryFromJson.from_json(result, *args, **kwargs) - - def children(self): - return [self.child] - - @staticmethod - def from_json(obj: Union[list, dict], *args, **kwargs): - if isinstance(obj, list): - return [EateryFromJson.from_json(elem, *args, **kwargs) for elem in obj] - else: - return Eatery.from_json(obj) - - def description(self): - return "EateryFromJson" - diff --git a/src/alert/controllers/api/dfg/nodes/system/ConvertToJson.py b/src/alert/controllers/api/dfg/nodes/system/ConvertToJson.py deleted file mode 100644 index 3894201..0000000 --- a/src/alert/controllers/api/dfg/nodes/system/ConvertToJson.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Union - -from event.datatype.Event import Event -from api.dfg.nodes.DfgNode import DfgNode -from eatery.datatype.Eatery import Eatery - - -class ConvertToJson(DfgNode): - def __init__(self, child: DfgNode): - self.child = child - - def __call__(self, *args, **kwargs): - result = self.child(*args, **kwargs) - return ConvertToJson.to_json(result, *args, **kwargs) - - def children(self): - return [self.child] - - @staticmethod - def to_json(obj: Union[list, Eatery, Event], *args, **kwargs): - if isinstance(obj, list): - return [ConvertToJson.to_json(elem, *args, **kwargs) for elem in obj] - else: - return obj.to_json( - tzinfo=kwargs.get("tzinfo"), - start=kwargs.get("start"), - end=kwargs.get("end"), - ) - - def description(self): - return "ConvertToJson" diff --git a/src/alert/controllers/api/dfg/nodes/system/DictResponseWrapper.py b/src/alert/controllers/api/dfg/nodes/system/DictResponseWrapper.py deleted file mode 100644 index db3b67d..0000000 --- a/src/alert/controllers/api/dfg/nodes/system/DictResponseWrapper.py +++ /dev/null @@ -1,20 +0,0 @@ -from api.dfg.nodes.DfgNode import DfgNode -from api.util.json import success_json, error_json - -class DictResponseWrapper(DfgNode): - - def __init__(self, child: DfgNode, re_raise_exceptions: bool = False): - self.child = child - self.re_raise_exceptions = re_raise_exceptions - - def __call__(self, *args, **kwargs): - try: - return success_json(self.child(*args, **kwargs)) - - except Exception as e: - if self.re_raise_exceptions: - raise e - return error_json(str(e)) - - def description(self): - return "DictResponseWrapper" diff --git a/src/alert/controllers/api/dfg/nodes/system/EateryGenerator.py b/src/alert/controllers/api/dfg/nodes/system/EateryGenerator.py deleted file mode 100644 index 0e98995..0000000 --- a/src/alert/controllers/api/dfg/nodes/system/EateryGenerator.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Optional - -from api.dfg.nodes.DfgNode import DfgNode -from eatery.datatype.Eatery import Eatery, EateryID - - -class EateryGenerator(DfgNode): - def __init__( - self, - eatery_id: EateryID, - events_dfg: Optional[DfgNode] = None, - wait_times_dfg: Optional[DfgNode] = None, - ): - self.eatery_id = eatery_id - self.events_dfg = events_dfg - self.wait_times_dfg = wait_times_dfg - - def __call__(self, *args, **kwargs) -> list: - return Eatery( - id=self.eatery_id, - events=None - if self.events_dfg is None - else self.events_dfg(*args, **kwargs), - wait_times=None - if self.wait_times_dfg is None - else self.wait_times_dfg(*args, **kwargs), - ) - - def description(self): - return "EateryGenerator" diff --git a/src/alert/controllers/api/dfg/nodes/system/InMemoryCache.py b/src/alert/controllers/api/dfg/nodes/system/InMemoryCache.py deleted file mode 100644 index 9966a6c..0000000 --- a/src/alert/controllers/api/dfg/nodes/system/InMemoryCache.py +++ /dev/null @@ -1,77 +0,0 @@ -import time - -from api.dfg.nodes.DfgNode import DfgNode -from typing import Optional - -from api.dfg.nodes.system.ConvertToJson import ConvertToJson - - -class DataSnapshot: - - def __init__(self, args, kwargs, data, time): - self.data = data - self.recorded_time = time - self.args = args - self.kwargs = kwargs - - def is_usable_snapshot(self, oldest_possible_time, args, kwargs): - return args == self.args and kwargs == self.kwargs and self.recorded_time >= oldest_possible_time - - def get_data(self): - return self.data - - def to_json(self): - return ConvertToJson.to_json(self.data, *self.args, **self.kwargs) - - def get_recorded_time(self): - return self.recorded_time - - -class InMemoryCache(DfgNode): - - def __init__(self, child, expiration: float = 3600, max_size: int = 5): - self.child = child - self.expiration = expiration - self.max_size = max_size - self.snapshots: list[DataSnapshot] = [] - - def current_time(self): - return time.time() - - def fifo_index(self): - if len(self.snapshots) == 0: - return None - oldest_snapshot_time = self.snapshots[0].get_recorded_time() - oldest_snapshot_index = 0 - for i in range(1, len(self.snapshots)): - if self.snapshots[i].get_recorded_time() < oldest_snapshot_time: - oldest_snapshot_time = self.snapshots[i].get_recorded_time() - oldest_snapshot_index = i - return oldest_snapshot_index - - def __call__(self, *args, **kwargs): - should_reload = kwargs.get("reload") - best_snapshot = None - for snapshot in self.snapshots: - if not should_reload and snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): - if best_snapshot == None or snapshot.recorded_time > best_snapshot.recorded_time: - best_snapshot = snapshot - if best_snapshot is not None: - return snapshot.get_data() - - new_snapshot = DataSnapshot(args, kwargs, self.child(*args, **kwargs), self.current_time()) - if len(self.snapshots) < self.max_size: - self.snapshots.append(new_snapshot) - else: - index_to_replace = self.fifo_index() - self.snapshots[index_to_replace] = new_snapshot - return new_snapshot.get_data() - - def to_json(self, *args, **kwargs): - for snapshot in self.snapshots: - if snapshot.is_usable_snapshot(self.current_time() - self.expiration, args, kwargs): - return snapshot.to_json() - return ConvertToJson.to_json(self.child(*args, **kwargs), *args, **kwargs) - - def description(self): - return "InMemoryCache" diff --git a/src/alert/controllers/api/dfg/nodes/system/LeftMerge.py b/src/alert/controllers/api/dfg/nodes/system/LeftMerge.py deleted file mode 100644 index 3f1edbb..0000000 --- a/src/alert/controllers/api/dfg/nodes/system/LeftMerge.py +++ /dev/null @@ -1,58 +0,0 @@ -from api.dfg.nodes.DfgNode import DfgNode -from typing import Callable, TypeVar, Any -from functools import cmp_to_key -T = TypeVar("T") - -# Merges two lists of objects, combining objects with matching IDs (keys of object in left array have precedence if -# conflict) -class LeftMerge(DfgNode): - - def __init__(self, left: DfgNode, right: DfgNode, comparator: Callable[[T, T], int]): - self.left = left - self.right = right - self.comparator = comparator - - def children(self): - return [self.left, self.right] - - def __call__(self: Any, *args, **kwargs): - left_lst = sorted(self.left(*args, **kwargs), key=cmp_to_key(self.comparator)) - right_lst = sorted(self.right(*args, **kwargs), key=cmp_to_key(self.comparator)) - left_json = _pop_first(left_lst) - right_json = _pop_first(right_lst) - merged_lst = [] - while left_json is not None and right_json is not None: - if self.comparator(left_json, right_json) == 0: - merged_json = {} - for key in right_json: - if right_json[key] is not None: - merged_json[key] = right_json[key] - for key in left_json: - if left_json[key] is not None: - merged_json[key] = left_json[key] - merged_lst.append(merged_json) - left_json = _pop_first(left_lst) - right_json = _pop_first(right_lst) - elif self.comparator(left_json, right_json) < 0: - merged_lst.append(left_json) - left_json = _pop_first(left_lst) - else: - merged_lst.append(right_json) - right_json = _pop_first(right_lst) - if left_json is not None: - merged_lst.append(left_json) - if right_json is not None: - merged_lst.append(right_json) - merged_lst.extend(left_lst) - merged_lst.extend(right_lst) - return merged_lst - - def description(self): - return "LeftMerge" - - -def _pop_first(lst: list): - try: - return lst.pop(0) - except IndexError: - return None diff --git a/src/alert/controllers/api/dfg/nodes/system/Mapping.py b/src/alert/controllers/api/dfg/nodes/system/Mapping.py deleted file mode 100644 index 8028549..0000000 --- a/src/alert/controllers/api/dfg/nodes/system/Mapping.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Any, Callable - -from api.dfg.nodes.DfgNode import DfgNode -from eatery.datatype.Eatery import Eatery, EateryID - - -class Mapping(DfgNode): - def __init__(self, child: DfgNode, fn: Callable[[Any, dict], DfgNode]): - self.child = child - self.fn = fn - - def __call__(self, *args, **kwargs) -> list: - result = [] - cache = {} - for ele in self.child(*args, **kwargs): - dfg = self.fn(ele, cache) - result.append(dfg(*args, **kwargs)) - return result - - def description(self): - return "Mapping" diff --git a/src/alert/controllers/api/dfg/nodes/wait_times/WaitTimeFilter.py b/src/alert/controllers/api/dfg/nodes/wait_times/WaitTimeFilter.py deleted file mode 100644 index a2830fb..0000000 --- a/src/alert/controllers/api/dfg/nodes/wait_times/WaitTimeFilter.py +++ /dev/null @@ -1,39 +0,0 @@ -from api.datatype.WaitTimesDay import WaitTimesDay -from api.dfg.nodes.DfgNode import DfgNode - - -# Removes all wait times that are not part of the eatery's events - -class WaitTimeFilter(DfgNode): - def __init__(self, child: DfgNode): - self.child = child - - def children(self): - return [self.child] - - def __call__(self, *args, **kwargs): - eateries = self.child(*args, **kwargs) - result = [] - for eatery in eateries: - if eatery.wait_times is None: - result.append(eatery.clone()) - else: - wait_times_filtered = [] - for day_wait_times in eatery.wait_times: - filtered_data = [] - for wait_time_data in day_wait_times.data: - eatery_events = eatery.events() - if any([wait_time_data.timestamp in event for event in eatery_events]): - filtered_data.append(wait_time_data) - if len(filtered_data) > 0: - wait_times_filtered.append(WaitTimesDay( - canonical_date=day_wait_times.canonical_date, - data=filtered_data - )) - eatery_clone = eatery.clone() - eatery_clone.wait_times = wait_times_filtered - result.append(eatery_clone) - return result - - def description(self): - return "WaitTimeFilter" diff --git a/src/alert/controllers/api/dfg/nodes/wait_times/WaitTimes.py b/src/alert/controllers/api/dfg/nodes/wait_times/WaitTimes.py deleted file mode 100644 index f98aeb2..0000000 --- a/src/alert/controllers/api/dfg/nodes/wait_times/WaitTimes.py +++ /dev/null @@ -1,135 +0,0 @@ -from datetime import date, timedelta - -import pytz -from event.datatype.Event import Event -from api.datatype.WaitTime import WaitTime -from api.datatype.WaitTimesDay import WaitTimesDay -from api.dfg.nodes.DfgNode import DfgNode -from api.models import TransactionHistoryStore -from api.util.time import combined_timestamp -from django.db.models import Avg -from eatery.datatype.Eatery import Eatery, EateryID - - -class WaitTimes(DfgNode): - def __init__(self, eatery_id: EateryID, cache): - self.eatery_id = eatery_id - self.cache = cache - - def __call__(self, *args, **kwargs) -> list[Eatery]: - if "transactions" not in self.cache: - transactions = {} - date = kwargs.get("start") - while date <= kwargs.get("end"): - transactions[date] = [] - past_days = [] - for i in range(1, 13): - past_days.append(date - timedelta(days=7 * i)) - transaction_avg_counts = ( - TransactionHistoryStore.objects.filter(canonical_date__in=past_days) - .values("eatery_id", "block_end_time") - .annotate(transaction_avg=Avg("transaction_count")) - ) - for unit in transaction_avg_counts: - transactions[date].append(unit) - date += timedelta(days=1) - self.cache["transactions"] = transactions - - eatery_wait_times = [] - for date in self.cache["transactions"]: - eatery_transaction_avgs = [ - transaction_avg - for transaction_avg in self.cache["transactions"][date] - if transaction_avg["eatery_id"] == self.eatery_id.value - ] - date_wait_times = WaitTimes.generate_eatery_wait_times_by_day( - self.eatery_id, date, eatery_transaction_avgs, kwargs.get("tzinfo") - ) - if date_wait_times is not None: - eatery_wait_times.append(date_wait_times) - - return eatery_wait_times - - # Expected amount of time (in seconds) for the length of the line to decrease by 1 person - # Returns [lower, expected, upper] - @staticmethod - def line_decrease_by_one_time(eatery_id: EateryID) -> list[int]: - if eatery_id == EateryID.MACS_CAFE: - return [24, 27, 30] - elif eatery_id == EateryID.MATTINS_CAFE: - return [9, 15, 21] - elif eatery_id == EateryID.TERRACE: - return [15, 27, 36] - elif eatery_id == EateryID.OKENSHIELDS: - return [4, 8, 12] - else: - return [18, 21, 24] - - # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the - # amount of time to check out Returns [lower, expected, upper] - @staticmethod - def base_time_to_get_food(eatery_id: EateryID) -> list[int]: - if eatery_id == EateryID.MACS_CAFE: - return [240, 300, 360] - elif eatery_id == EateryID.MATTINS_CAFE: - return [150, 210, 270] - elif eatery_id == EateryID.TERRACE: - return [180, 300, 420] - elif eatery_id == EateryID.OKENSHIELDS: - return [80, 120, 180] - else: - return [180, 240, 300] - - @staticmethod - def generate_eatery_wait_times_by_day( - eatery_id: EateryID, date: date, transactions: list, tzinfo: pytz.tzinfo - ) -> WaitTimesDay: - wait_times_data = [] - customers_waiting_in_line = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - for index in reversed(range(0, len(transactions))): - base_times = WaitTimes.base_time_to_get_food(eatery_id) - line_decrease_times = WaitTimes.line_decrease_by_one_time(eatery_id) - # we assume all the guests in this transaction bucket showed up [how_long_ago_guest_arrival] minutes ago - how_long_ago_guest_arrival = ( - base_times[1] - + line_decrease_times[1] * transactions[index]["transaction_avg"] - ) - prev_bucket_guest_arrival = int(how_long_ago_guest_arrival // (5 * 60)) - if prev_bucket_guest_arrival > 9: - pass - # TODO: Send a slack error here instead - # print("Fatal Wait Times Error - prev_bucket_guest_arrival far too large.") - else: - customers_waiting_in_line[prev_bucket_guest_arrival] += transactions[ - index - ]["transaction_avg"] - num_customers = customers_waiting_in_line.pop(0) - wait_time_low = int( - base_times[0] + line_decrease_times[0] * num_customers - ) - wait_time_expected = int( - base_times[1] + line_decrease_times[1] * num_customers - ) - wait_time_high = int( - base_times[2] + line_decrease_times[2] * num_customers - ) - - customers_waiting_in_line.append(0.0) - block_end_time = transactions[index]["block_end_time"] - timestamp = int( - combined_timestamp(date, block_end_time, tzinfo) - 5 * 60 / 2 - ) - wait_times_data.insert( - 0, - WaitTime( - timestamp=timestamp, - wait_time_low=wait_time_low, - wait_time_expected=wait_time_expected, - wait_time_high=wait_time_high, - ), - ) - - return WaitTimesDay(canonical_date=date, data=wait_times_data) - - def description(self): - return "WaitTimes" diff --git a/src/alert/controllers/api/management/commands/export_db_snapshot.py b/src/alert/controllers/api/management/commands/export_db_snapshot.py deleted file mode 100644 index 8918a36..0000000 --- a/src/alert/controllers/api/management/commands/export_db_snapshot.py +++ /dev/null @@ -1,107 +0,0 @@ -import json -from datetime import datetime -from pathlib import Path - -import api.models as models -import api.serializers as serializers -import eatery.serializers as eatery_serializers -import pytz -from api.util.constants import SnapshotFileName -from django.core.management.base import BaseCommand - - -class Command(BaseCommand): - help = "Saves the current state of the database" - - def write_to_file( - self, serializer, db_objects, folder_path, file_name_enum: SnapshotFileName - ): - file_path = f"{folder_path}/{file_name_enum.value}" - serialized_lst = serializer(db_objects, many=True) - with open(file_path, "w") as file: - for obj in serialized_lst.data: - file.write(json.dumps(obj) + "\n") - - def handle(self, *args, **options): - tzinfo = pytz.timezone("US/Eastern") - time = datetime.now(tzinfo).strftime("%Y-%m-%d %H:%M:%S") - folder_path = f"db_snapshots/{time}" - Path(folder_path).mkdir(parents=True, exist_ok=True) - - eateries = models.EateryStore.objects.all() - self.write_to_file( - eatery_serializers.EateryStoreSerializer, - eateries, - folder_path, - SnapshotFileName.EATERY_STORE, - ) - - alerts = models.AlertStore.objects.filter( - end_timestamp__gte=datetime.now().timestamp() - ) - self.write_to_file( - serializers.AlertStoreSerializer, - alerts, - folder_path, - SnapshotFileName.ALERT_STORE, - ) - - menus = models.MenuStore.objects.all() - self.write_to_file( - serializers.MenuStoreSerializer, - menus, - folder_path, - SnapshotFileName.MENU_STORE, - ) - - categories = models.CategoryStore.objects.all() - self.write_to_file( - serializers.CategoryStoreSerializer, - categories, - folder_path, - SnapshotFileName.CATEGORY_STORE, - ) - - items = models.ItemStore.objects.all() - self.write_to_file( - serializers.ItemStoreSerializer, - items, - folder_path, - SnapshotFileName.ITEM_STORE, - ) - - subitems = models.SubItemStore.objects.all() - self.write_to_file( - serializers.SubItemStoreSerializer, - subitems, - folder_path, - SnapshotFileName.SUBITEM_STORE, - ) - - category_item_associations = models.CategoryItemAssociation.objects.all() - self.write_to_file( - serializers.CategoryItemAssociationSerializer, - category_item_associations, - folder_path, - SnapshotFileName.CATEGORY_ITEM_ASSOCIATION, - ) - - schedule_exceptions = models.ScheduleException.objects.all() - self.write_to_file( - serializers.ScheduleExceptionSerializer, - schedule_exceptions, - folder_path, - SnapshotFileName.SCHEDULE_EXCEPTION, - ) - - day_of_week_event_schedules = models.RepeatingEventSchedule.objects.all() - self.write_to_file( - serializers.RepeatingEventScheduleSerializer, - day_of_week_event_schedules, - folder_path, - SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE, - ) - - # # TODO: Need to filter here for only the valid schedules - # event_schedules = models.EventSchedule.objects.all() - # self.write_to_file(event_schedules, f"{folder_path}/{SnapshotFileName.EVENT_SCHEDULE}") diff --git a/src/alert/controllers/api/management/commands/ingest_db_snapshot.py b/src/alert/controllers/api/management/commands/ingest_db_snapshot.py deleted file mode 100644 index e978dc0..0000000 --- a/src/alert/controllers/api/management/commands/ingest_db_snapshot.py +++ /dev/null @@ -1,63 +0,0 @@ -import json - -import api.serializers as serializers -import eatery.serializers as eatery_serializers -from api.util.constants import SnapshotFileName -from django.core.management.base import BaseCommand - - -class Command(BaseCommand): - help = ( - "Overrides current state of the db with a db snapshot. Takes --input argument" - ) - - def add_arguments(self, parser): - parser.add_argument("--input", type=str) - - # Only writes data if the table has been flushed - def ingest_data(self, serializer, folder_path: str, file_name: SnapshotFileName): - with open(f"{folder_path}/{file_name.value}", "r") as file: - json_objs = [] - for line in file: - if len(line) > 2: - json_objs.append(json.loads(line)) - serialized_objs = serializer(data=json_objs, many=True) - serialized_objs.is_valid() - serialized_objs.save() - - def handle(self, *args, **options): - folder_path = options["input"] - self.ingest_data( - eatery_serializers.EateryStoreSerializer, - folder_path, - SnapshotFileName.EATERY_STORE, - ) - self.ingest_data( - serializers.AlertStoreSerializer, folder_path, SnapshotFileName.ALERT_STORE - ) - self.ingest_data( - serializers.MenuStoreSerializer, folder_path, SnapshotFileName.MENU_STORE - ) - self.ingest_data( - serializers.CategoryStoreSerializer, - folder_path, - SnapshotFileName.CATEGORY_STORE, - ) - self.ingest_data( - serializers.ItemStoreSerializer, folder_path, SnapshotFileName.ITEM_STORE - ) - self.ingest_data( - serializers.SubItemStoreSerializer, - folder_path, - SnapshotFileName.SUBITEM_STORE, - ) - self.ingest_data( - serializers.CategoryItemAssociationSerializer, - folder_path, - SnapshotFileName.CATEGORY_ITEM_ASSOCIATION, - ) - self.ingest_data( - serializers.RepeatingEventScheduleSerializer, - folder_path, - SnapshotFileName.DAY_OF_WEEK_EVENT_SCHEDULE, - ) diff --git a/src/alert/controllers/api/management/commands/ingest_log_transactions.py b/src/alert/controllers/api/management/commands/ingest_log_transactions.py deleted file mode 100644 index c0e14b3..0000000 --- a/src/alert/controllers/api/management/commands/ingest_log_transactions.py +++ /dev/null @@ -1,34 +0,0 @@ -from datetime import datetime -from django.core.management.base import BaseCommand -import json - -from api.controllers.create_transaction import CreateTransactionController -from api.controllers.delete_all_transactions import DeleteAllTransactionsController - -class Command(BaseCommand): - help = 'Transfers log data from the old storage format (log.txt file) into the TransactionHistoryStore table' - - def add_arguments(self, parser): - parser.add_argument('--input', type=str) - - def handle(self, *args, **options): - num_deleted = DeleteAllTransactionsController().process() - counter = 0 - num_inserted = 0 - file_path = options["input"] - # Transaction Histories used to be stored in a giant log file. Ingest that log file into the db - with open(file_path, "r") as log: - for line in log: - try: - data = json.loads(line) - timestamp = datetime.strptime(data['TIMESTAMP'], '%Y-%m-%d %I:%M:%S %p') - if counter % 100 == 1: - print(timestamp) - if timestamp.year == 2021 and timestamp.month > 7: - counter += 1 - inserted = CreateTransactionController(data).process() - num_inserted += inserted - except Exception as e: - pass - print("{} Entries Deleted".format(num_deleted)) - print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file diff --git a/src/alert/controllers/api/management/commands/ingest_recent_transactions.py b/src/alert/controllers/api/management/commands/ingest_recent_transactions.py deleted file mode 100644 index ea55659..0000000 --- a/src/alert/controllers/api/management/commands/ingest_recent_transactions.py +++ /dev/null @@ -1,26 +0,0 @@ -# Transaction Histories used to be stored in a giant log file. Ingest that log file into the db - -import requests -import os -from requests.structures import CaseInsensitiveDict -from django.core.management.base import BaseCommand -from api.controllers.create_transaction import CreateTransactionController - -class Command(BaseCommand): - help = 'Fetches transaction data from a vendor API and adds it to our transaction history database' - - def handle(self, *args, **options): - endpoint = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" - headers = CaseInsensitiveDict() - token = os.environ.get("CORNELL_VENDOR_TOKEN") - api_key = os.environ.get("CORNELL_VENDOR_API_KEY") - headers["Accept"] = "application/json" - headers["Authorization"] = "Bearer {}".format(token) - headers["X-Api-Key"] = api_key - resp = requests.get(endpoint, headers=headers) - num_inserted = 0 - if resp.status_code == 200: - res = CreateTransactionController(resp.json()).process() - if res["success"]: - num_inserted = res["result"]["num_inserted"] - # print("{} Entries Inserted".format(num_inserted)) \ No newline at end of file diff --git a/src/alert/controllers/api/migrations/0001_initial.py b/src/alert/controllers/api/migrations/0001_initial.py deleted file mode 100644 index 2984ce0..0000000 --- a/src/alert/controllers/api/migrations/0001_initial.py +++ /dev/null @@ -1,166 +0,0 @@ -# Generated by Django 4.0 on 2022-01-13 17:27 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='EateryStore', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('name', models.CharField(blank=True, max_length=40)), - ('menu_summary', models.CharField(blank=True, max_length=60)), - ('image_url', models.URLField(blank=True)), - ('location', models.CharField(blank=True, max_length=30)), - ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), - ('online_order_url', models.URLField(blank=True)), - ('latitude', models.FloatField(blank=True, null=True)), - ('longitude', models.FloatField(blank=True, null=True)), - ('payment_accepts_meal_swipes', models.BooleanField(blank=True, null=True)), - ('payment_accepts_brbs', models.BooleanField(blank=True, null=True)), - ('payment_accepts_cash', models.BooleanField(blank=True, null=True)), - ], - ), - migrations.CreateModel( - name='ItemStore', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=40)), - ('description', models.CharField(blank=True, max_length=200)), - ('base_price', models.FloatField(blank=True, null=True)), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), - ], - ), - migrations.CreateModel( - name='TransactionHistoryStore', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('canonical_date', models.DateField()), - ('block_end_time', models.TimeField()), - ('transaction_count', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), - ], - ), - migrations.CreateModel( - name='SubItemStore', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('additional_price', models.FloatField(blank=True, null=True)), - ('total_price', models.FloatField(blank=True, null=True)), - ('name', models.CharField(max_length=40)), - ('item_subsection', models.CharField(max_length=40)), - ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.itemstore')), - ], - ), - migrations.CreateModel( - name='ReportStore', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('type', models.CharField(max_length=200)), - ('content', models.TextField()), - ('created_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), - ], - ), - migrations.CreateModel( - name='MenuStore', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=40)), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), - ], - ), - migrations.CreateModel( - name='DayOfWeekEventSchedule', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('event_description', models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], max_length=10)), - ('day_of_week', models.CharField(choices=[('Monday', 'Monday'), ('Tuesday', 'Tuesday'), ('Wednesday', 'Wednesday'), ('Thursday', 'Thursday'), ('Friday', 'Friday'), ('Saturday', 'Saturday'), ('Sunday', 'Sunday')], max_length=10)), - ('start', models.TimeField()), - ('end', models.TimeField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.menustore')), - ], - ), - migrations.CreateModel( - name='DateEventSchedule', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('event_description', models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], max_length=10)), - ('canonical_date', models.DateField()), - ('start_timestamp', models.IntegerField()), - ('end_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.menustore')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='ClosedEventSchedule', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('event_description', models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], max_length=10)), - ('canonical_date', models.DateField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='CategoryStore', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('category', models.CharField(max_length=40)), - ('menu', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.menustore')), - ], - ), - migrations.CreateModel( - name='CategoryItemAssociation', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.categorystore')), - ('item', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.itemstore')), - ], - ), - migrations.CreateModel( - name='AlertStore', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('description', models.CharField(max_length=250)), - ('start_timestamp', models.IntegerField()), - ('end_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.eaterystore')), - ], - ), - migrations.AddIndex( - model_name='transactionhistorystore', - index=models.Index(fields=['canonical_date'], name='api_transac_canonic_7e6d4f_idx'), - ), - migrations.AlterUniqueTogether( - name='transactionhistorystore', - unique_together={('eatery_id', 'block_end_time', 'canonical_date')}, - ), - migrations.AlterUniqueTogether( - name='menustore', - unique_together={('eatery', 'name')}, - ), - migrations.AlterUniqueTogether( - name='dayofweekeventschedule', - unique_together={('eatery', 'day_of_week', 'event_description')}, - ), - migrations.AlterUniqueTogether( - name='categorystore', - unique_together={('menu', 'category')}, - ), - ] diff --git a/src/alert/controllers/api/migrations/0002_repeatingeventschedule_scheduleexception_and_more.py b/src/alert/controllers/api/migrations/0002_repeatingeventschedule_scheduleexception_and_more.py deleted file mode 100644 index 08a4d33..0000000 --- a/src/alert/controllers/api/migrations/0002_repeatingeventschedule_scheduleexception_and_more.py +++ /dev/null @@ -1,111 +0,0 @@ -# Generated by Django 4.0 on 2022-09-14 15:53 - -import django.core.validators -from django.db import migrations, models -import django.db.models.deletion -import re - - -class Migration(migrations.Migration): - - dependencies = [ - ('eatery', '0001_initial'), - ('api', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='RepeatingEventSchedule', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('event_description', models.CharField(choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General')], max_length=10)), - ('start_date', models.DateField()), - ('start_time', models.TimeField()), - ('end_time', models.TimeField()), - ('repeat_interval', models.IntegerField()), - ('offset_lst', models.CharField(max_length=100, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')])), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore')), - ], - ), - migrations.CreateModel( - name='ScheduleException', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('date', models.DateField()), - ('exception_type', models.CharField(choices=[('closed', 'Closed'), ('modified', 'Modified')], max_length=10)), - ('start_time', models.TimeField(blank=True, null=True)), - ('end_time', models.TimeField(blank=True, null=True)), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore')), - ('parent', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.repeatingeventschedule')), - ], - options={ - 'abstract': False, - }, - ), - migrations.RemoveField( - model_name='dateeventschedule', - name='eatery', - ), - migrations.RemoveField( - model_name='dateeventschedule', - name='menu', - ), - migrations.AlterUniqueTogether( - name='dayofweekeventschedule', - unique_together=None, - ), - migrations.RemoveField( - model_name='dayofweekeventschedule', - name='eatery', - ), - migrations.RemoveField( - model_name='dayofweekeventschedule', - name='menu', - ), - migrations.AlterField( - model_name='alertstore', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='itemstore', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='menustore', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='reportstore', - name='eatery', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.AlterField( - model_name='transactionhistorystore', - name='eatery', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eaterystore'), - ), - migrations.DeleteModel( - name='ClosedEventSchedule', - ), - migrations.DeleteModel( - name='DateEventSchedule', - ), - migrations.DeleteModel( - name='DayOfWeekEventSchedule', - ), - migrations.DeleteModel( - name='EateryStore', - ), - migrations.AddField( - model_name='repeatingeventschedule', - name='menu', - field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='api.menustore'), - ), - migrations.AlterUniqueTogether( - name='repeatingeventschedule', - unique_together={('eatery', 'event_description')}, - ), - ] diff --git a/src/alert/controllers/api/migrations/__init__.py b/src/alert/controllers/api/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/alert/controllers/api/models/TransactionModel.py b/src/alert/controllers/api/models/TransactionModel.py deleted file mode 100644 index 4ce0976..0000000 --- a/src/alert/controllers/api/models/TransactionModel.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.db import models -from eatery.models import EateryStore - -# [transaction_count] transactions at [name] in time range [block_end_time - 5 minutes, block_end_time] on [canonical_date] - - -class TransactionHistoryStore(models.Model): - class Meta: - unique_together = ("eatery_id", "block_end_time", "canonical_date") - indexes = [models.Index(fields=["canonical_date"])] - - eatery = models.ForeignKey(EateryStore, on_delete=models.DO_NOTHING) - canonical_date = models.DateField() - block_end_time = models.TimeField() - transaction_count = models.IntegerField() diff --git a/src/alert/controllers/api/models/__init__.py b/src/alert/controllers/api/models/__init__.py deleted file mode 100644 index 3c045c1..0000000 --- a/src/alert/controllers/api/models/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Need to expose models to django. -# In this app, models should be imported directly from api.models, not from api.models.package - -from eatery.models import EateryStore - -#from .alert.models.AlertModel import AlertStore -#from .EventScheduleModel import EventSchedule, RepeatingEventSchedule, ScheduleException -#from ...reports.ReportModel import ReportStore -from .TransactionModel import TransactionHistoryStore diff --git a/src/alert/controllers/api/serializers.py b/src/alert/controllers/api/serializers.py deleted file mode 100644 index a7e9c09..0000000 --- a/src/alert/controllers/api/serializers.py +++ /dev/null @@ -1,45 +0,0 @@ -from rest_framework import serializers - -import api.models as models - -class MenuStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.MenuStore - fields = "__all__" - - -class ItemStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.ItemStore - fields = "__all__" - - -class SubItemStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.SubItemStore - fields = "__all__" - - -class CategoryStoreSerializer(serializers.ModelSerializer): - class Meta: - model = models.CategoryStore - fields = "__all__" - - -class CategoryItemAssociationSerializer(serializers.ModelSerializer): - class Meta: - model = models.CategoryItemAssociation - fields = "__all__" - -class RepeatingEventScheduleSerializer(serializers.ModelSerializer): - class Meta: - model = models.RepeatingEventSchedule - fields = "__all__" - - -class ScheduleExceptionSerializer(serializers.ModelSerializer): - class Meta: - model = models.ScheduleException - fields = "__all__" - - diff --git a/src/alert/controllers/misc_code.py b/src/alert/controllers/misc_code.py deleted file mode 100644 index ee54407..0000000 --- a/src/alert/controllers/misc_code.py +++ /dev/null @@ -1,27 +0,0 @@ -# EateriesFromDB -"""alerts=EateriesFromDB.alerts(serialized_eatery["id"], serialized_alerts), - -@staticmethod - def alerts(eatery_id: int, serialized_alerts: list[dict]): - return [ - EateriesFromDB.alert_from_serialized(alert) - for alert in serialized_alerts - if alert["eatery"] == eatery_id - ] - - @staticmethod - def alert_from_serialized(serialized_alert: dict): - return EateryAlert( - id=serialized_alert["id"], - description=serialized_alert["description"], - start_timestamp=serialized_alert["start_timestamp"], - end_timestamp=serialized_alert["end_timestamp"], - - - alerts = AlertStore.objects.filter( - end_timestamp__gte=datetime.now().timestamp(), - start_timestamp__lte=datetime.now().timestamp(), - ) - serialized_alerts = AlertStoreSerializer(data=alerts, many=True) - serialized_alerts.is_valid() - )""" From 731d88afdf121eefca05729715b478c0382fa0b9 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 27 Sep 2023 15:04:05 -0700 Subject: [PATCH 158/305] fix bug with removing auth (#60) --- src/eatery_blue_backend/settings.py | 5 ----- src/person/serializers.py | 15 --------------- src/person/urls.py | 1 - 3 files changed, 21 deletions(-) diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 2687315..fe3ee0f 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -136,8 +136,3 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -# Authentication Controller -AUTH_PASSWORD_SALT = os.getenv("AUTH_PASSWORD_SALT") -ACCESS_TOKEN_AGE = 60 * 15 # 15 minutes -LOCAL_AUTH = os.getenv("LOCAL") == "True" \ No newline at end of file diff --git a/src/person/serializers.py b/src/person/serializers.py index f4330f3..a5e3942 100644 --- a/src/person/serializers.py +++ b/src/person/serializers.py @@ -12,18 +12,3 @@ class ChefSerializer(serializers.ModelSerializer): class Meta: model = Chef fields = ['id', 'user', 'eateries_managed'] - -class AuthenticateSerializer(serializers.ModelSerializer): - access_token = serializers.SerializerMethodField(method_name="get_access_token") - - class Meta: - model = User - fields = ( - "access_token", - User.USERNAME_FIELD, - "first_name", - "last_name", - ) - - def get_access_token(self, instance): - return self.context.get("access_token") \ No newline at end of file diff --git a/src/person/urls.py b/src/person/urls.py index 2c85b1b..9c7b45d 100644 --- a/src/person/urls.py +++ b/src/person/urls.py @@ -8,5 +8,4 @@ urlpatterns = [ path('', include(router.urls)), - path("authenticate/", views.AuthenticateView.as_view(), name="authenticate"), ] \ No newline at end of file From d24452773c353cc5eec103b0c8f31579b1fde6b6 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 27 Sep 2023 15:30:49 -0700 Subject: [PATCH 159/305] remove swipe data from codebase (#61) --- src/cdn_parser/controllers/populate_models.py | 4 - src/eatery/serializers.py | 6 +- src/eatery_blue_backend/settings.py | 1 - src/swipe/__init__.py | 0 src/swipe/admin.py | 3 - src/swipe/apps.py | 6 - src/swipe/controllers/populate_wait_times.py | 139 ------------------ src/swipe/migrations/0001_initial.py | 29 ---- src/swipe/migrations/__init__.py | 0 src/swipe/models.py | 14 -- src/swipe/serializers.py | 32 ---- src/swipe/tests.py | 3 - src/swipe/views.py | 7 - 13 files changed, 1 insertion(+), 243 deletions(-) delete mode 100644 src/swipe/__init__.py delete mode 100644 src/swipe/admin.py delete mode 100644 src/swipe/apps.py delete mode 100644 src/swipe/controllers/populate_wait_times.py delete mode 100644 src/swipe/migrations/0001_initial.py delete mode 100644 src/swipe/migrations/__init__.py delete mode 100644 src/swipe/models.py delete mode 100644 src/swipe/serializers.py delete mode 100644 src/swipe/tests.py delete mode 100644 src/swipe/views.py diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py index bca4780..f752ad5 100644 --- a/src/cdn_parser/controllers/populate_models.py +++ b/src/cdn_parser/controllers/populate_models.py @@ -3,7 +3,6 @@ from django.core import management from event.models import Event from eatery.controllers.populate_eatery import PopulateEateryController -from swipe.controllers.populate_wait_times import PopulateWaitTimeController from event.controllers.populate_event import PopulateEventController from item.controllers.populate_item import PopulateItemController from category.controllers.populate_category import PopulateCategoryController @@ -55,9 +54,6 @@ def process(self): print("Populating eateries") PopulateEateryController().process(json_eateries) - print("Populating wait times") - PopulateWaitTimeController().process(json_eateries) - print("Populating events") events_dict = PopulateEventController().process(json_eateries) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 7c75258..b7291cd 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -1,9 +1,6 @@ from rest_framework import serializers from eatery.models import Eatery from event.serializers import EventSerializer, EventSerializerSimple -from swipe.serializers import WaitTimeSerializer - - class EaterySerializer(serializers.ModelSerializer): id = serializers.IntegerField() @@ -19,7 +16,6 @@ class EaterySerializer(serializers.ModelSerializer): payment_accepts_brbs = serializers.BooleanField(allow_null=True) payment_accepts_cash = serializers.BooleanField(allow_null=True) - wait_time = WaitTimeSerializer(many=True, read_only=True) events = EventSerializer(many=True, read_only=True) def create(self, validated_data): @@ -28,7 +24,7 @@ def create(self, validated_data): class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'wait_time', 'events'] + fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] class EaterySerializerSimple(serializers.ModelSerializer): diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index fe3ee0f..01396e0 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -45,7 +45,6 @@ "report", "item", "category", - "swipe", "cdn_parser", "rest_framework", "person", diff --git a/src/swipe/__init__.py b/src/swipe/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/swipe/admin.py b/src/swipe/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/src/swipe/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/src/swipe/apps.py b/src/swipe/apps.py deleted file mode 100644 index 0781444..0000000 --- a/src/swipe/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class SwipeConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'swipe' diff --git a/src/swipe/controllers/populate_wait_times.py b/src/swipe/controllers/populate_wait_times.py deleted file mode 100644 index 9ef1dd8..0000000 --- a/src/swipe/controllers/populate_wait_times.py +++ /dev/null @@ -1,139 +0,0 @@ -from datetime import datetime -from swipe.serializers import WaitTimeSerializer -from swipe.models import WaitTime -from eatery.datatype.Eatery import EateryID -from eatery.util.constants import vendor_name_to_internal_id, dining_id_to_internal_id, CORNELL_VENDOR_URL, DAY_OF_WEEK_LIST -from django.core.exceptions import ObjectDoesNotExist -import requests -import os - - -class PopulateWaitTimeController(): - def __init__(self): - self = self - - def get_json(self): - try: - headers = { - 'Content-type': 'application/json', - 'x-api-key': os.environ['VENDOR_API_KEY'], - 'Authorization': os.environ['VENDOR_BEARER_TOKEN'] - } - response = requests.get(CORNELL_VENDOR_URL, headers=headers) - except Exception as e: - raise e - if response.status_code < 400: - return response.json() - - # A serializable wait_time object - @staticmethod - def construct_wait_time(eatery_id, low_time, expected_time, high_time, day, hour, trials): - return { - 'eatery': eatery_id, - 'wait_time_low': low_time, - 'wait_time_expected': expected_time, - 'wait_time_high': high_time, - 'day': day, - 'hour': hour, - 'trials': trials - } - - # Running average of with new_value being incorporated into the old_avg, where - # old_avg had trials amount of values - @staticmethod - def running_average(old_avg, new_value, trials): - return int((old_avg*trials + new_value)/(trials+1)) - - # Expected amount of time (in seconds) for the length of the line to decrease by 1 person - # Returns [lower, expected, upper] - @staticmethod - def line_decrease_by_one_time(eatery_id: EateryID) -> list[int]: - if eatery_id == EateryID.MACS_CAFE: - return [24, 27, 30] - elif eatery_id == EateryID.MATTINS_CAFE: - return [9, 15, 21] - elif eatery_id == EateryID.TERRACE: - return [15, 27, 36] - elif eatery_id == EateryID.OKENSHIELDS: - return [4, 8, 12] - else: - return [18, 21, 24] - - # Expected amount of time (in seconds) for a person to get food, assuming an empty eatery, not including the - # amount of time to check out Returns [lower, expected, upper] - @staticmethod - def base_time_to_get_food(eatery_id: EateryID) -> list[int]: - if eatery_id == EateryID.MACS_CAFE: - return [240, 300, 360] - elif eatery_id == EateryID.MATTINS_CAFE: - return [150, 210, 270] - elif eatery_id == EateryID.TERRACE: - return [60, 90, 120] - elif eatery_id == EateryID.OKENSHIELDS: - return [90, 120, 180] - else: - return [180, 240, 300] - - def process(self, json_eateries): - """ - From an swipe json from CDN, create wait_times for that eatery and add to event model. - """ - # If are populating for the first time, create wait_times for all eateries with no values - if not WaitTime.objects.all(): - for json_eatery in json_eateries: - eatery_id = dining_id_to_internal_id(int(json_eatery["id"])).value - for i in range(7): - for j in range(24): - data = self.construct_wait_time(eatery_id,0,0,0,DAY_OF_WEEK_LIST[i], j,0) - wait_time = WaitTimeSerializer(data=data) - if wait_time.is_valid(): - wait_time.save() - else: - print(wait_time.errors) - print(json_eatery["name"]) - return - - # Iterate through all eateries in the json and add waittimes as they appear from the dining swipe json - json_swipe = self.get_json() - if not json_swipe: - return - json_swipe_units = json_swipe.get("UNITS") - if json_swipe_units is None: - # Error in requesting vendor data - print(json_swipe) - return json_swipe - unit_info = {vendor_name_to_internal_id(x["UNIT_NAME"]).value: x["CROWD_COUNT"] for x in json_swipe_units} - for json_eatery in json_eateries: - eatery_id = dining_id_to_internal_id(int(json_eatery["id"])).value - formatted_datetime = datetime.strptime(json_swipe["TIMESTAMP"], '%Y-%m-%d %I:%M:%S %p') - day = DAY_OF_WEEK_LIST[formatted_datetime.weekday()] - hour = formatted_datetime.hour - count = int(unit_info.get(eatery_id, 0)) - - # Calculate the expected wait time for the eatery at the given time - get_food_time = self.base_time_to_get_food(eatery_id) if count > 0 else [0, 0, 0] - low_time = count*self.line_decrease_by_one_time(eatery_id)[0] + get_food_time[0] - expected_time = count*self.line_decrease_by_one_time(eatery_id)[1] + get_food_time[1] - high_time = count*self.line_decrease_by_one_time(eatery_id)[2] + get_food_time[2] - - # Update the wait time for the eatery at the given time or create a new wait time if it doesn't exist - try: - wait_time = WaitTime.objects.get(eatery=eatery_id, day=day, hour=hour) - trials = wait_time.trials - new_low = self.running_average(wait_time.wait_time_low, low_time, trials) - new_expected = self.running_average(wait_time.wait_time_expected, expected_time, trials) - new_high = self.running_average(wait_time.wait_time_high, high_time, trials) - - data = self.construct_wait_time(eatery_id,new_low,new_expected,new_high, day, hour, wait_time.trials+1) - serialized = WaitTimeSerializer(wait_time, data=data, partial=True) - except ObjectDoesNotExist: - data = self.construct_wait_time(eatery_id,low_time,expected_time,high_time, day, hour, 1) - serialized = WaitTimeSerializer(data=data) - if serialized.is_valid(): - serialized.save() - else: - print(serialized.errors) - print(serialized) - return serialized.errors - - \ No newline at end of file diff --git a/src/swipe/migrations/0001_initial.py b/src/swipe/migrations/0001_initial.py deleted file mode 100644 index 33ff783..0000000 --- a/src/swipe/migrations/0001_initial.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 4.0 on 2023-04-22 20:06 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('eatery', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='WaitTime', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('wait_time_high', models.IntegerField()), - ('wait_time_expected', models.IntegerField()), - ('wait_time_low', models.IntegerField()), - ('day', models.TextField(default=0)), - ('hour', models.IntegerField(default=0)), - ('trials', models.IntegerField(default=1)), - ('eatery', models.ForeignKey(default=0, on_delete=django.db.models.deletion.DO_NOTHING, related_name='wait_time', to='eatery.eatery')), - ], - ), - ] diff --git a/src/swipe/migrations/__init__.py b/src/swipe/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/swipe/models.py b/src/swipe/models.py deleted file mode 100644 index afd17db..0000000 --- a/src/swipe/models.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.db import models -from eatery.models import Eatery -# Create your models here. - -class WaitTime(models.Model): - id = models.AutoField(primary_key=True) - eatery = models.ForeignKey(Eatery, related_name = "wait_time", on_delete=models.DO_NOTHING, default=0) - wait_time_high = models.IntegerField() - wait_time_expected = models.IntegerField() - wait_time_low = models.IntegerField() - day = models.TextField(default=0) - hour = models.IntegerField(default=0) - trials = models.IntegerField(default = 1) - diff --git a/src/swipe/serializers.py b/src/swipe/serializers.py deleted file mode 100644 index 2c9ee5a..0000000 --- a/src/swipe/serializers.py +++ /dev/null @@ -1,32 +0,0 @@ -from rest_framework import serializers -from swipe.models import WaitTime -from datetime import datetime -from eatery.util.constants import DAY_OF_WEEK_LIST - -class DayWaitTimeSerializer(serializers.ListSerializer): - - def to_representation(self, data): - day = DAY_OF_WEEK_LIST[datetime.now().weekday()] - data = data.filter(day=day) - return super().to_representation(data) - -class WaitTimeSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(required=False, read_only=True) - wait_time_high = serializers.IntegerField() - wait_time_expected = serializers.IntegerField() - wait_time_low = serializers.IntegerField() - day = serializers.CharField( - allow_null=True, allow_blank=True, default=None - ) - hour = serializers.IntegerField() - trials = serializers.IntegerField() - - def create(self, validated_data): - wait_time, _ = WaitTime.objects.get_or_create(**validated_data) - return wait_time - - class Meta: - model = WaitTime - fields = ["id", "eatery", "day", "hour", "wait_time_high", "wait_time_expected", "wait_time_low", "trials"] - list_serializer_class = DayWaitTimeSerializer - \ No newline at end of file diff --git a/src/swipe/tests.py b/src/swipe/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/swipe/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/swipe/views.py b/src/swipe/views.py deleted file mode 100644 index 32d7823..0000000 --- a/src/swipe/views.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import viewsets -from swipe.models import WaitTime -from swipe.serializers import WaitTimeSerializer - -class WaitTimeViewSet(viewsets.ModelViewSet): - queryset = WaitTime.objects.all() - serializer_class = WaitTimeSerializer \ No newline at end of file From 9ba2bd8d2a9b964022fc33a2b2ab416595655d56 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Thu, 5 Oct 2023 11:35:31 -0700 Subject: [PATCH 160/305] add permissions to protect endpoints (#62) --- src/alert/permissions.py | 13 +++++++++++++ src/alert/views.py | 4 +++- src/eatery/permissions.py | 13 +++++++++++++ src/eatery/urls.py | 4 ++-- src/eatery/views.py | 30 +++++++----------------------- src/eatery_blue_backend/urls.py | 1 - src/event/urls.py | 7 ------- src/event/views.py | 14 ++------------ src/person/permissions.py | 17 +++++++++++++++++ src/person/views.py | 5 ++++- src/report/permissions.py | 11 +++++++++++ src/report/views.py | 4 +++- 12 files changed, 75 insertions(+), 48 deletions(-) create mode 100644 src/alert/permissions.py create mode 100644 src/eatery/permissions.py delete mode 100644 src/event/urls.py create mode 100644 src/person/permissions.py create mode 100644 src/report/permissions.py diff --git a/src/alert/permissions.py b/src/alert/permissions.py new file mode 100644 index 0000000..6e50db2 --- /dev/null +++ b/src/alert/permissions.py @@ -0,0 +1,13 @@ +from rest_framework import permissions + +class AlertPermission(permissions.BasePermission): + + def has_permission(self, request, view): + if view.action in ['list', 'retrieve']: + return True + return request.user.is_staff + + def has_object_permission(self, request, view, obj): + if view.action in ['retrieve']: + return True + return request.user.is_staff \ No newline at end of file diff --git a/src/alert/views.py b/src/alert/views.py index f797211..0c1ad5f 100644 --- a/src/alert/views.py +++ b/src/alert/views.py @@ -1,7 +1,9 @@ from rest_framework import viewsets from alert.models import Alert from alert.serializers import AlertSerializer +from .permissions import AlertPermission class AlertViewSet(viewsets.ModelViewSet): queryset = Alert.objects.all() - serializer_class = AlertSerializer \ No newline at end of file + serializer_class = AlertSerializer + permission_classes = [AlertPermission] \ No newline at end of file diff --git a/src/eatery/permissions.py b/src/eatery/permissions.py new file mode 100644 index 0000000..42af072 --- /dev/null +++ b/src/eatery/permissions.py @@ -0,0 +1,13 @@ +from rest_framework import permissions + +class EateryPermission(permissions.BasePermission): + + def has_permission(self, request, view): + if view.action in ['list', 'retrieve']: + return True + return request.user.is_staff + + def has_object_permission(self, request, view, obj): + if view.action in ['retrieve']: + return True + return request.user.is_staff \ No newline at end of file diff --git a/src/eatery/urls.py b/src/eatery/urls.py index bd3d014..c7a635c 100644 --- a/src/eatery/urls.py +++ b/src/eatery/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from eatery.views import EateryViewSet, EateryViewSetSimple +from eatery.views import EateryViewSet, GetEateriesSimple eateries_list = EateryViewSet.as_view({ 'get':'list', @@ -16,5 +16,5 @@ urlpatterns = [ path("", eateries_list, name='eateries-list'), path("/", eatery_list, name='eatery-list'), - path("simple/", EateryViewSetSimple.as_view({'get':'list'}), name='eateries-simple'), + path("simple/", GetEateriesSimple.as_view(), name='eateries-simple'), ] \ No newline at end of file diff --git a/src/eatery/views.py b/src/eatery/views.py index e98443e..dea086a 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -5,6 +5,7 @@ from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import viewsets +from .permissions import EateryPermission from eatery.datatype.Eatery import EateryID @@ -18,6 +19,7 @@ class EateryViewSet(viewsets.ModelViewSet): """ queryset = Eatery.objects.all() serializer_class = EaterySerializer + permission_classes = [EateryPermission] def get_object(self): queryset = self.filter_queryset(self.get_queryset()) @@ -37,17 +39,7 @@ def get_object(self): self.check_object_permissions(self.request, obj) return obj -class GetEateries(APIView): - def get(self, request): - """ - Update models with CDN data - Update models with image information - Update models with - """ - return - -class UpdateEatery(APIView): - def post(self, request): + def update(self, request, *args, **kwargs): text_params = request.POST if not verify_json_fields( text_params, @@ -82,18 +74,10 @@ def post(self, request): except Exception as e: return JsonResponse(error_json(str(e))) - -class GetEateries(APIView): - def get(self, request): - eateries = EaterySerializer(Eatery.objects.all(), many=True) - if not eateries.data: - return JsonResponse(error_json("eateries is empty")) - - return JsonResponse(success_json(eateries.data)) - -class EateryViewSetSimple(viewsets.ModelViewSet): +class GetEateriesSimple(APIView): """ View all eateries with less information """ - queryset = Eatery.objects.all() - serializer_class = EaterySerializerSimple \ No newline at end of file + def get(self, request): + eateries = EaterySerializerSimple(Eatery.objects.all(), many=True) + return Response(eateries.data) \ No newline at end of file diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index bf1a988..c9ae339 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -19,7 +19,6 @@ urlpatterns = [ path("admin/", admin.site.urls), path("eatery/", include("eatery.urls")), - path("event/", include("event.urls")), path("cdn/", include("cdn_parser.urls")), path("report/", include("report.urls")), path("alert/", include("alert.urls")), diff --git a/src/event/urls.py b/src/event/urls.py deleted file mode 100644 index ce94074..0000000 --- a/src/event/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.urls import path - -from event.views import PopulateEventView - -urlpatterns = [ - path("populate/", PopulateEventView.as_view(), name="menu"), -] diff --git a/src/event/views.py b/src/event/views.py index 24e582a..91ea44a 100644 --- a/src/event/views.py +++ b/src/event/views.py @@ -1,13 +1,3 @@ -import json -from datetime import date, timedelta +from django.shortcuts import render -import pytz -from django.http import JsonResponse -from eatery.datatype.Eatery import EateryID -from rest_framework.views import APIView -from rest_framework import generics - -from eatery.util.json import FieldType, error_json, success_json, verify_json_fields - -class PopulateEventView(generics.GenericAPIView): - pass \ No newline at end of file +# Create your views here. diff --git a/src/person/permissions.py b/src/person/permissions.py new file mode 100644 index 0000000..4b9a4a8 --- /dev/null +++ b/src/person/permissions.py @@ -0,0 +1,17 @@ +from rest_framework import permissions + +class StudentPermission(permissions.BasePermission): + + def has_permission(self, request, view): + return False + + def has_object_permission(self, request, view, obj): + return False + +class ChefPermission(permissions.BasePermission): + + def has_permission(self, request, view): + return False + + def has_object_permission(self, request, view, obj): + return False \ No newline at end of file diff --git a/src/person/views.py b/src/person/views.py index 778d6e7..2395fc3 100644 --- a/src/person/views.py +++ b/src/person/views.py @@ -1,11 +1,14 @@ from rest_framework import viewsets from person.models import Student, Chef from person.serializers import StudentSerializer, ChefSerializer +from .permissions import StudentPermission, ChefPermission class StudentViewSet(viewsets.ModelViewSet): queryset = Student.objects.all() serializer_class = StudentSerializer + permission_classes = [StudentPermission] class ChefViewSet(viewsets.ModelViewSet): queryset = Chef.objects.all() - serializer_class = ChefSerializer \ No newline at end of file + serializer_class = ChefSerializer + permission_classes = [ChefPermission] \ No newline at end of file diff --git a/src/report/permissions.py b/src/report/permissions.py new file mode 100644 index 0000000..f38c0f4 --- /dev/null +++ b/src/report/permissions.py @@ -0,0 +1,11 @@ +from rest_framework import permissions + +class ReportPermission(permissions.BasePermission): + + def has_permission(self, request, view): + if view.action in ['create']: + return True + return request.user.is_staff + + def has_object_permission(self, request, view, obj): + return request.user.is_staff \ No newline at end of file diff --git a/src/report/views.py b/src/report/views.py index 943fe71..b5e9c1f 100644 --- a/src/report/views.py +++ b/src/report/views.py @@ -1,7 +1,9 @@ from rest_framework import viewsets from report.models import Report from report.serializers import ReportSerializer +from .permissions import ReportPermission class ReportViewSet(viewsets.ModelViewSet): queryset = Report.objects.all() - serializer_class = ReportSerializer \ No newline at end of file + serializer_class = ReportSerializer + permission_classes = [ReportPermission] \ No newline at end of file From 2629d91a8f24d95552dd1e3b1f41cea607e05b0a Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Mon, 9 Oct 2023 18:34:57 -0700 Subject: [PATCH 161/305] fix macs image link (#63) --- src/eatery/util/eatery_store.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index 8440c63..ebe945a 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -32,7 +32,7 @@ {"id": 31, "menu_summary": "Soups, salads, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg"} {"id": 32, "menu_summary": "Burgers, pasta, quesadillas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg"} {"id": 33, "name": "Terrace", "menu_summary": "Burrito and rice bowls, pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg", "location": "Statler Hall", "campus_area": "Central", "latitude": 42.446267, "longitude": -76.482314, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} -{"id": 34, "name": "Mac's Caf\u00e9", "menu_summary": "Flatbreads, pasta, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/macs", "location": "Statler Hotel", "campus_area": "Central", "latitude": 42.445921, "longitude": -76.481984, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} +{"id": 34, "name": "Mac's Caf\u00e9", "menu_summary": "Flatbreads, pasta, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Macs-Cafe.jpg", "location": "Statler Hotel", "campus_area": "Central", "latitude": 42.445921, "longitude": -76.481984, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} {"id": 35, "name": "Temple of Zeus", "menu_summary": "Coffee, pastries, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Zeus.jpg", "location": "Goldwin Smith Hall", "campus_area": "Central", "latitude": 42.449091, "longitude": -76.483414, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 36, "name": "Gimme Coffee", "menu_summary": "Coffee, pastries, tea", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Gimme-Coffee.jpg", "location": "Gates Hall", "campus_area": "Central", "latitude": 42.444958, "longitude": -76.481169, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} From 3cb9a47c3e1cda651c5dce97be6563a5d2a2259e Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 11 Oct 2023 14:49:55 -0700 Subject: [PATCH 162/305] Update deploy-dev.yml --- .github/workflows/deploy-dev.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 89dc66a..9236f28 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -38,6 +38,8 @@ jobs: key: ${{ secrets.DEV_SERVER_KEY }} script: | export IMAGE_TAG=${{ steps.vars.outputs.sha_short }} + echo $IMAGE_TAG + echo "debugging" cd docker-compose docker stack rm the-stack sleep 20s From b9473ddde78f45b457e4448b4de543ff3172fb15 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 11 Oct 2023 14:56:41 -0700 Subject: [PATCH 163/305] Update deploy-dev.yml --- .github/workflows/deploy-dev.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 9236f28..abec60a 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -38,10 +38,8 @@ jobs: key: ${{ secrets.DEV_SERVER_KEY }} script: | export IMAGE_TAG=${{ steps.vars.outputs.sha_short }} - echo $IMAGE_TAG - echo "debugging" cd docker-compose - docker stack rm the-stack + docker stack rm thestack sleep 20s - docker stack deploy -c docker-compose.yml the-stack + docker stack deploy -c docker-compose.yml thestack docker system prune -a From 4839817c745d7ffaeb8e17e66b3b05ec1f1f5190 Mon Sep 17 00:00:00 2001 From: Mateo Date: Tue, 17 Oct 2023 20:17:10 -0400 Subject: [PATCH 164/305] remove unused routes and views --- src/cdn_parser/urls.py | 6 ------ src/cdn_parser/views.py | 11 +---------- src/eatery_blue_backend/urls.py | 1 - 3 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 src/cdn_parser/urls.py diff --git a/src/cdn_parser/urls.py b/src/cdn_parser/urls.py deleted file mode 100644 index ea7f571..0000000 --- a/src/cdn_parser/urls.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.urls import path -from cdn_parser.views import PopulateModels - -urlpatterns = [ - path("populate/", PopulateModels.as_view(), name="populate") -] \ No newline at end of file diff --git a/src/cdn_parser/views.py b/src/cdn_parser/views.py index 9e4815e..91ea44a 100644 --- a/src/cdn_parser/views.py +++ b/src/cdn_parser/views.py @@ -1,12 +1,3 @@ from django.shortcuts import render -from django.http import JsonResponse -from rest_framework import generics - -from eatery.util.json import FieldType, error_json, success_json, verify_json_fields -from cdn_parser.controllers.populate_models import CornellDiningNowController - -class PopulateModels(generics.GenericAPIView): - def get(self, request): - CornellDiningNowController().process() - return JsonResponse(success_json("Populated all models")) +# Create your views here. diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index c9ae339..3dee7a6 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -19,7 +19,6 @@ urlpatterns = [ path("admin/", admin.site.urls), path("eatery/", include("eatery.urls")), - path("cdn/", include("cdn_parser.urls")), path("report/", include("report.urls")), path("alert/", include("alert.urls")), path("person/", include("person.urls")), From 2d283961b6f60f8c645b981efd6fbb949c1e7d0c Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 18 Oct 2023 17:23:14 -0400 Subject: [PATCH 165/305] Remove RPCC and Carol's Cafe --- src/cdn_parser/controllers/populate_models.py | 7 +++++++ src/eatery/models.py | 5 +++++ src/eatery/util/eatery_store.txt | 2 -- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py index f752ad5..60523ea 100644 --- a/src/cdn_parser/controllers/populate_models.py +++ b/src/cdn_parser/controllers/populate_models.py @@ -2,6 +2,7 @@ from eatery.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id from django.core import management from event.models import Event +from eatery.models import Eatery from eatery.controllers.populate_eatery import PopulateEateryController from event.controllers.populate_event import PopulateEventController from item.controllers.populate_item import PopulateItemController @@ -51,9 +52,15 @@ def process(self): json_eateries = self.get_json() Event.truncate() + Eatery.truncate() + + print(Eatery.objects.filter(id=16)) + print("Populating eateries") PopulateEateryController().process(json_eateries) + print(Eatery.objects.filter(id=16)) + print("Populating events") events_dict = PopulateEventController().process(json_eateries) diff --git a/src/eatery/models.py b/src/eatery/models.py index 5845f29..e515788 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -24,4 +24,9 @@ class CampusArea(models.TextChoices): payment_accepts_meal_swipes = models.BooleanField(null=True, blank=True) payment_accepts_brbs = models.BooleanField(null=True, blank=True) payment_accepts_cash = models.BooleanField(null=True, blank=True) + + @classmethod + def truncate(cls): + with connection.cursor() as cursor: + cursor.execute('TRUNCATE TABLE {} CASCADE'.format(cls._meta.db_table)) diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index ebe945a..7f9d817 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -7,7 +7,6 @@ {"id": 6, "menu_summary": "Sandwiches, salads, hay", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg"} {"id": 7, "menu_summary": "Bagels, bagels, bagels", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg"} {"id": 8, "menu_summary": "Sandwiches, salads, drinks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg"} -{"id": 9, "name": "Carol's Caf\u00e9", "menu_summary": "Carols, wine, christmas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Carols-Cafe.jpg", "location": "Balch Hall", "campus_area": "North", "latitude": 42.4533011, "longitude": -76.4791678, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": false, "online_order_url": null} {"id": 10, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg"} {"id": 11, "menu_summary": "Ice cream and more ice cream", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg"} {"id": 12, "menu_summary": "Smoothies, quesadillas, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg"} @@ -26,7 +25,6 @@ {"id": 25, "menu_summary": "Freshly remodeled dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg"} {"id": 26, "menu_summary": "The only central campus dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg"} {"id": 27, "menu_summary": "Gluten free dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg"} -{"id": 28, "menu_summary": "Sushi Fridays, freshly made pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/RPCC-Marketplace.jpg"} {"id": 29, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg"} {"id": 30, "menu_summary": "Coffee, tea, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg"} {"id": 31, "menu_summary": "Soups, salads, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg"} From f73e861d95a2cd9586f9fbd8c9477d24076ed28d Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 18 Oct 2023 17:36:55 -0400 Subject: [PATCH 166/305] Removed prints --- src/cdn_parser/controllers/populate_models.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py index 60523ea..70dee34 100644 --- a/src/cdn_parser/controllers/populate_models.py +++ b/src/cdn_parser/controllers/populate_models.py @@ -54,13 +54,9 @@ def process(self): Event.truncate() Eatery.truncate() - print(Eatery.objects.filter(id=16)) - print("Populating eateries") PopulateEateryController().process(json_eateries) - print(Eatery.objects.filter(id=16)) - print("Populating events") events_dict = PopulateEventController().process(json_eateries) From dd83d023f3b12d777e150e5a8e901a92d8af890e Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 1 Nov 2023 17:16:41 -0400 Subject: [PATCH 167/305] Resolve empty menus issue for cafes --- src/category/controllers/populate_category.py | 6 +++--- src/item/controllers/populate_item.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index e13b39b..d7febcc 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -76,9 +76,9 @@ def process(self, events_dict, json_eateries): else: continue - is_cafe = "Cafe" in { - eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] - } + desired_descriptions = {"Cafe", "Cart", "Coffee Shop", "Food Court"} + eatery_descriptions = {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} + is_cafe = bool(desired_descriptions.intersection(eatery_descriptions)) """ For every event in an eatery --> for every menu in an eatery --> get categories diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index d892bf8..554b0da 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -54,9 +54,9 @@ def process(self, categories_dict, json_eateries): iter = list(eatery_menus.keys()) i = 0 - is_cafe = "Cafe" in { - eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] - } + desired_descriptions = {"Cafe", "Cart", "Coffee Shop", "Food Court"} + eatery_descriptions = {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} + is_cafe = bool(desired_descriptions.intersection(eatery_descriptions)) json_dates = json_eatery["operatingHours"] for json_date in json_dates: From 2574fca3748c0613749721d9915f115cc93c5fa6 Mon Sep 17 00:00:00 2001 From: Mateo Date: Wed, 8 Nov 2023 17:23:01 -0500 Subject: [PATCH 168/305] improve serialization --- src/category/serializers.py | 9 ++++++++- src/eatery/serializers.py | 17 ++++++++++++++++- src/eatery/views.py | 13 ++++++++++++- src/event/serializers.py | 14 ++++++++++++-- src/item/serializers.py | 9 ++++++++- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/category/serializers.py b/src/category/serializers.py index 1043bb5..7fc052f 100644 --- a/src/category/serializers.py +++ b/src/category/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from category.models import Category -from item.serializers import ItemSerializer +from item.serializers import ItemSerializer, ItemReadSerializer class CategorySerializer(serializers.ModelSerializer): @@ -15,3 +15,10 @@ def create(self, validated_data): class Meta: model = Category fields = ["id", "category", "event", "items"] + +class CategoryReadSerializer(serializers.ModelSerializer): + items = ItemReadSerializer(many=True, read_only=True) + + class Meta: + model = Category + fields = ["category", "items"] diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index b7291cd..b299756 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from eatery.models import Eatery -from event.serializers import EventSerializer, EventSerializerSimple +from event.serializers import EventSerializer, EventReadSerializer, EventSerializerSimple class EaterySerializer(serializers.ModelSerializer): id = serializers.IntegerField() @@ -21,6 +21,21 @@ class EaterySerializer(serializers.ModelSerializer): def create(self, validated_data): eatery, _ = Eatery.objects.get_or_create(**validated_data) return eatery + +class EateryReadSerializer(serializers.ModelSerializer): + id = serializers.IntegerField() + name = serializers.CharField() + menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") + image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + location = serializers.CharField(allow_null=True) + campus_area = serializers.CharField(allow_null=True) + online_order_url = serializers.URLField(allow_null=True) + latitude = serializers.FloatField(allow_null=True) + longitude = serializers.FloatField(allow_null=True) + payment_accepts_meal_swipes = serializers.BooleanField(allow_null=True) + payment_accepts_brbs = serializers.BooleanField(allow_null=True) + payment_accepts_cash = serializers.BooleanField(allow_null=True) + events = EventReadSerializer(many=True, read_only=True) class Meta: model = Eatery diff --git a/src/eatery/views.py b/src/eatery/views.py index dea086a..11081b4 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -1,4 +1,4 @@ -from eatery.serializers import EaterySerializer, EaterySerializerSimple +from eatery.serializers import EaterySerializer, EateryReadSerializer, EaterySerializerSimple from eatery.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse from django.shortcuts import get_object_or_404 @@ -21,6 +21,17 @@ class EateryViewSet(viewsets.ModelViewSet): serializer_class = EaterySerializer permission_classes = [EateryPermission] + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + serializer = EateryReadSerializer(instance) + return Response(serializer.data) + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + serializer = EateryReadSerializer(queryset, many=True) + return Response(serializer.data) + + def get_object(self): queryset = self.filter_queryset(self.get_queryset()) lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field diff --git a/src/event/serializers.py b/src/event/serializers.py index 94f7fd9..40c0798 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from event.models import Event -from category.serializers import CategorySerializer +from category.serializers import CategorySerializer, CategoryReadSerializer from datetime import datetime class EventSerializer(serializers.ModelSerializer): @@ -21,7 +21,17 @@ class Meta: model = Event fields = ["id", "eatery", "event_description", "start", "end", "menu"] +class EventReadSerializer(serializers.ModelSerializer): + event_description = serializers.CharField(allow_null=True, allow_blank=True, default=None) + start = serializers.IntegerField() + end = serializers.IntegerField() + menu = CategoryReadSerializer(many=True, read_only=True) + + class Meta: + model = Event + fields = ["event_description", "start", "end", "menu"] + class EventSerializerSimple(serializers.ModelSerializer): class Meta: model = Event - fields = ["id", "eatery", "event_description", "start", "end"] + fields = ["id", "event_description", "start", "end"] diff --git a/src/item/serializers.py b/src/item/serializers.py index 772bf4c..658e779 100644 --- a/src/item/serializers.py +++ b/src/item/serializers.py @@ -11,4 +11,11 @@ def create(self, validated_data): class Meta: model = Item - fields = ['id', 'category', 'name'] \ No newline at end of file + fields = ['id', 'category', 'name'] + +class ItemReadSerializer(serializers.ModelSerializer): + name = serializers.CharField(default = "Item") + + class Meta: + model = Item + fields = ['name'] \ No newline at end of file From a3350a6d33386d85503a4f5ab9223c4b4959f758 Mon Sep 17 00:00:00 2001 From: Mateo Date: Wed, 8 Nov 2023 17:51:23 -0500 Subject: [PATCH 169/305] temp commit --- src/eatery/serializers.py | 13 +++++++++++++ src/eatery/urls.py | 3 ++- src/eatery/views.py | 10 +++++++++- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index b299756..61e220a 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -50,3 +50,16 @@ class EaterySerializerSimple(serializers.ModelSerializer): class Meta: model = Eatery fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + +class EateryByDaySerializer(serializers.ModelSerializer): + menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") + image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + events = serializers.SerializerMethodField(many=True, read_only=True) + + class Meta: + model = Eatery + fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + + def get_events(self, obj): + events = obj.events.filter(start__lte=self.context['date'], end__gte=self.context['date']) + return EventReadSerializer(events, many=True).data \ No newline at end of file diff --git a/src/eatery/urls.py b/src/eatery/urls.py index c7a635c..e4519cf 100644 --- a/src/eatery/urls.py +++ b/src/eatery/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from eatery.views import EateryViewSet, GetEateriesSimple +from eatery.views import EateryViewSet, GetEateriesSimple, GetEateriesByDay eateries_list = EateryViewSet.as_view({ 'get':'list', @@ -17,4 +17,5 @@ path("", eateries_list, name='eateries-list'), path("/", eatery_list, name='eatery-list'), path("simple/", GetEateriesSimple.as_view(), name='eateries-simple'), + path("day//", GetEateriesByDay.as_view(), name="eateries-day"), ] \ No newline at end of file diff --git a/src/eatery/views.py b/src/eatery/views.py index 11081b4..20b8017 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -1,4 +1,4 @@ -from eatery.serializers import EaterySerializer, EateryReadSerializer, EaterySerializerSimple +from eatery.serializers import EaterySerializer, EateryReadSerializer, EaterySerializerSimple, EateryByDaySerializer from eatery.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse from django.shortcuts import get_object_or_404 @@ -91,4 +91,12 @@ class GetEateriesSimple(APIView): """ def get(self, request): eateries = EaterySerializerSimple(Eatery.objects.all(), many=True) + return Response(eateries.data) + +class GetEateriesByDay(APIView): + """ + View all eateries with events on a specific day + """ + def get(self, request, date): + eateries = EateryByDaySerializer(Eatery.objects.all(), many=True) return Response(eateries.data) \ No newline at end of file From de58f7d550df2517b82cdd3e5e0600b2f030e13f Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 8 Nov 2023 15:20:19 -0800 Subject: [PATCH 170/305] Custom get all reports route (#68) * remove unused routes and views * add custom retrieve --- .envrctemplate | 3 ++- src/cdn_parser/urls.py | 6 ------ src/cdn_parser/views.py | 11 +---------- src/eatery_blue_backend/urls.py | 1 - src/report/permissions.py | 2 +- src/report/views.py | 15 ++++++++++++++- 6 files changed, 18 insertions(+), 20 deletions(-) delete mode 100644 src/cdn_parser/urls.py diff --git a/.envrctemplate b/.envrctemplate index dd54878..76756c7 100644 --- a/.envrctemplate +++ b/.envrctemplate @@ -8,4 +8,5 @@ export POSTGRES_PASSWORD= export POSTGRES_HOST= export POSTGRES_PORT= export VENDOR_API_KEY= -export VENDOR_BEARER_TOKEN= \ No newline at end of file +export VENDOR_BEARER_TOKEN= +export REPORT_KEY= \ No newline at end of file diff --git a/src/cdn_parser/urls.py b/src/cdn_parser/urls.py deleted file mode 100644 index ea7f571..0000000 --- a/src/cdn_parser/urls.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.urls import path -from cdn_parser.views import PopulateModels - -urlpatterns = [ - path("populate/", PopulateModels.as_view(), name="populate") -] \ No newline at end of file diff --git a/src/cdn_parser/views.py b/src/cdn_parser/views.py index 9e4815e..91ea44a 100644 --- a/src/cdn_parser/views.py +++ b/src/cdn_parser/views.py @@ -1,12 +1,3 @@ from django.shortcuts import render -from django.http import JsonResponse -from rest_framework import generics - -from eatery.util.json import FieldType, error_json, success_json, verify_json_fields -from cdn_parser.controllers.populate_models import CornellDiningNowController - -class PopulateModels(generics.GenericAPIView): - def get(self, request): - CornellDiningNowController().process() - return JsonResponse(success_json("Populated all models")) +# Create your views here. diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index c9ae339..3dee7a6 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -19,7 +19,6 @@ urlpatterns = [ path("admin/", admin.site.urls), path("eatery/", include("eatery.urls")), - path("cdn/", include("cdn_parser.urls")), path("report/", include("report.urls")), path("alert/", include("alert.urls")), path("person/", include("person.urls")), diff --git a/src/report/permissions.py b/src/report/permissions.py index f38c0f4..47a59f3 100644 --- a/src/report/permissions.py +++ b/src/report/permissions.py @@ -3,7 +3,7 @@ class ReportPermission(permissions.BasePermission): def has_permission(self, request, view): - if view.action in ['create']: + if view.action in ['create', 'custom_retrieve']: return True return request.user.is_staff diff --git a/src/report/views.py b/src/report/views.py index b5e9c1f..b638a40 100644 --- a/src/report/views.py +++ b/src/report/views.py @@ -2,8 +2,21 @@ from report.models import Report from report.serializers import ReportSerializer from .permissions import ReportPermission +from rest_framework.decorators import action +from rest_framework.response import Response +import os class ReportViewSet(viewsets.ModelViewSet): queryset = Report.objects.all() serializer_class = ReportSerializer - permission_classes = [ReportPermission] \ No newline at end of file + permission_classes = [ReportPermission] + + @action(url_path='custom_retrieve',detail=False, methods=['GET']) + def custom_retrieve(self, request, pk=None): + if 'key' not in request.headers: + return Response({"error": "no key"}, status=401) + if request.headers['key'] != os.environ.get('REPORT_KEY'): + return Response({"error": "invalid key"}, status=401) + report = Report.objects.all() + serializer = ReportSerializer(report, many=True) + return Response(serializer.data, status=200) \ No newline at end of file From 76ef9f385674944c758e2ce27d73f90bc4e87df7 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 15 Nov 2023 14:39:05 -0800 Subject: [PATCH 171/305] revert is_cafe breaking change (#72) --- src/category/controllers/populate_category.py | 6 +++--- src/item/controllers/populate_item.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index d7febcc..e13b39b 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -76,9 +76,9 @@ def process(self, events_dict, json_eateries): else: continue - desired_descriptions = {"Cafe", "Cart", "Coffee Shop", "Food Court"} - eatery_descriptions = {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} - is_cafe = bool(desired_descriptions.intersection(eatery_descriptions)) + is_cafe = "Cafe" in { + eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] + } """ For every event in an eatery --> for every menu in an eatery --> get categories diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 554b0da..2b22aac 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -54,9 +54,9 @@ def process(self, categories_dict, json_eateries): iter = list(eatery_menus.keys()) i = 0 - desired_descriptions = {"Cafe", "Cart", "Coffee Shop", "Food Court"} - eatery_descriptions = {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} - is_cafe = bool(desired_descriptions.intersection(eatery_descriptions)) + is_cafe = "Cafe" in { + eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] + } json_dates = json_eatery["operatingHours"] for json_date in json_dates: From 90c7d6879e2c2a54977d9e5af0dc6f6b6c8833e3 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 8 Nov 2023 15:20:19 -0800 Subject: [PATCH 172/305] Custom get all reports route (#68) * remove unused routes and views * add custom retrieve --- .envrctemplate | 3 ++- src/report/permissions.py | 2 +- src/report/views.py | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.envrctemplate b/.envrctemplate index dd54878..76756c7 100644 --- a/.envrctemplate +++ b/.envrctemplate @@ -8,4 +8,5 @@ export POSTGRES_PASSWORD= export POSTGRES_HOST= export POSTGRES_PORT= export VENDOR_API_KEY= -export VENDOR_BEARER_TOKEN= \ No newline at end of file +export VENDOR_BEARER_TOKEN= +export REPORT_KEY= \ No newline at end of file diff --git a/src/report/permissions.py b/src/report/permissions.py index f38c0f4..47a59f3 100644 --- a/src/report/permissions.py +++ b/src/report/permissions.py @@ -3,7 +3,7 @@ class ReportPermission(permissions.BasePermission): def has_permission(self, request, view): - if view.action in ['create']: + if view.action in ['create', 'custom_retrieve']: return True return request.user.is_staff diff --git a/src/report/views.py b/src/report/views.py index b5e9c1f..b638a40 100644 --- a/src/report/views.py +++ b/src/report/views.py @@ -2,8 +2,21 @@ from report.models import Report from report.serializers import ReportSerializer from .permissions import ReportPermission +from rest_framework.decorators import action +from rest_framework.response import Response +import os class ReportViewSet(viewsets.ModelViewSet): queryset = Report.objects.all() serializer_class = ReportSerializer - permission_classes = [ReportPermission] \ No newline at end of file + permission_classes = [ReportPermission] + + @action(url_path='custom_retrieve',detail=False, methods=['GET']) + def custom_retrieve(self, request, pk=None): + if 'key' not in request.headers: + return Response({"error": "no key"}, status=401) + if request.headers['key'] != os.environ.get('REPORT_KEY'): + return Response({"error": "invalid key"}, status=401) + report = Report.objects.all() + serializer = ReportSerializer(report, many=True) + return Response(serializer.data, status=200) \ No newline at end of file From a4a0cdcc4ee12d07d6535fc045bf6552044503aa Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 1 Nov 2023 17:16:41 -0400 Subject: [PATCH 173/305] Resolve empty menus issue for cafes --- src/category/controllers/populate_category.py | 6 +++--- src/item/controllers/populate_item.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index e13b39b..d7febcc 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -76,9 +76,9 @@ def process(self, events_dict, json_eateries): else: continue - is_cafe = "Cafe" in { - eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] - } + desired_descriptions = {"Cafe", "Cart", "Coffee Shop", "Food Court"} + eatery_descriptions = {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} + is_cafe = bool(desired_descriptions.intersection(eatery_descriptions)) """ For every event in an eatery --> for every menu in an eatery --> get categories diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index d892bf8..554b0da 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -54,9 +54,9 @@ def process(self, categories_dict, json_eateries): iter = list(eatery_menus.keys()) i = 0 - is_cafe = "Cafe" in { - eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] - } + desired_descriptions = {"Cafe", "Cart", "Coffee Shop", "Food Court"} + eatery_descriptions = {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} + is_cafe = bool(desired_descriptions.intersection(eatery_descriptions)) json_dates = json_eatery["operatingHours"] for json_date in json_dates: From 717c3331e21a8d2bfbdc9da31036bae569d257e5 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 15 Nov 2023 17:53:08 -0500 Subject: [PATCH 174/305] Create by day endpoint to reduce load times --- src/eatery/serializers.py | 23 +++++++++++++++++------ src/eatery/views.py | 8 ++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 61e220a..1e42c1f 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -1,6 +1,9 @@ from rest_framework import serializers from eatery.models import Eatery -from event.serializers import EventSerializer, EventReadSerializer, EventSerializerSimple +from event.models import Event +from event.serializers import EventSerializer, EventSerializerSimple, EventReadSerializer +from datetime import date, timedelta +from time import mktime class EaterySerializer(serializers.ModelSerializer): id = serializers.IntegerField() @@ -51,15 +54,23 @@ class Meta: model = Eatery fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] -class EateryByDaySerializer(serializers.ModelSerializer): +class EaterySerializerByDay(serializers.ModelSerializer): menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") - events = serializers.SerializerMethodField(many=True, read_only=True) + events = serializers.SerializerMethodField(many=True) + + def get_events(self, obj): + day = self.context.get("day") + today = date.today() + today = today + timedelta(days=day) + end_today = today + timedelta(days=1) + today_unix = mktime(today.timetuple()) + 18000 + end_today_unix = mktime(end_today.timetuple()) + 18000 + events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, end__lte=end_today_unix) + serializer = EventReadSerializer(instance=events, many=True) + return serializer.data class Meta: model = Eatery fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] - def get_events(self, obj): - events = obj.events.filter(start__lte=self.context['date'], end__gte=self.context['date']) - return EventReadSerializer(events, many=True).data \ No newline at end of file diff --git a/src/eatery/views.py b/src/eatery/views.py index 20b8017..511cef1 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -1,4 +1,4 @@ -from eatery.serializers import EaterySerializer, EateryReadSerializer, EaterySerializerSimple, EateryByDaySerializer +from eatery.serializers import EaterySerializer, EateryReadSerializer, EaterySerializerSimple, EaterySerializerByDay from eatery.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse from django.shortcuts import get_object_or_404 @@ -95,8 +95,8 @@ def get(self, request): class GetEateriesByDay(APIView): """ - View all eateries with events on a specific day + Get all eatery information by day """ - def get(self, request, date): - eateries = EateryByDaySerializer(Eatery.objects.all(), many=True) + def get(self, request, day): + eateries = EaterySerializerByDay(Eatery.objects.all(), many=True, context={"day": day}) return Response(eateries.data) \ No newline at end of file From ca62cef5251c35cb8784ab948bc6de4aa8137783 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 15 Nov 2023 14:55:44 -0800 Subject: [PATCH 175/305] implement adding times for external eateries (#70) --- src/cdn_parser/controllers/populate_models.py | 6 - src/event/controllers/populate_event.py | 44 +- static_sources/external_eateries.json | 1422 ++++++++++------- 3 files changed, 865 insertions(+), 607 deletions(-) diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py index 70dee34..f1abc5c 100644 --- a/src/cdn_parser/controllers/populate_models.py +++ b/src/cdn_parser/controllers/populate_models.py @@ -67,9 +67,3 @@ def process(self): PopulateItemController().process(categories_dict, json_eateries) print("Done populating") - - - - - - diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py index 7da41b4..5e0a78b 100644 --- a/src/event/controllers/populate_event.py +++ b/src/event/controllers/populate_event.py @@ -1,6 +1,7 @@ -from datetime import datetime +from datetime import datetime, timedelta from event.serializers import EventSerializer from eatery.util.constants import dining_id_to_internal_id +import json class PopulateEventController(): def __init__(self): @@ -36,8 +37,39 @@ def generate_events(self, json_eatery): return event.errors events.append(event.data["id"]) - + return events + + def generate_external_events(self, json_eatery): + json_dates = json_eatery["operatingHours"] + events = [] + for json_date in json_dates: + json_event = json_date["events"][0] + date = datetime.now() + while date.strftime("%A").lower() != json_date["weekday"].lower(): + date += timedelta(days=1) + start_string = json_event['start'] + start_timestamp = datetime(date.year, date.month, date.day, int(start_string[:2]), int(start_string[3:])).timestamp() + end_string = json_event['end'] + if int(end_string[:2]) < int(start_string[:2]): + date += timedelta(days=1) + end_timestamp = datetime(date.year, date.month, date.day, int(end_string[:2]), int(end_string[3:])).timestamp() + eatery_id = json_eatery["id"] + data = { + 'eatery': eatery_id, + 'event_description': json_event["descr"], + 'start' : start_timestamp, + 'end' : end_timestamp} + + event = EventSerializer(data=data) + + if event.is_valid(): + event.save() + else: + print(event.errors) + return event.errors + + events.append(event.data["id"]) return events def process(self, json_eateries): @@ -50,6 +82,10 @@ def process(self, json_eateries): events = self.generate_events(json_eatery) events_dict[eatery_id] = events - return events_dict + # create custom events for external eateries + with open("../static_sources/external_eateries.json", "r") as file: + json_obj = json.load(file) + for eatery in json_obj['eateries']: + events_dict[eatery['id']] = self.generate_external_events(eatery) - \ No newline at end of file + return events_dict diff --git a/static_sources/external_eateries.json b/static_sources/external_eateries.json index a2cf74f..c927f6a 100644 --- a/static_sources/external_eateries.json +++ b/static_sources/external_eateries.json @@ -1,600 +1,828 @@ { - "eateries": [ - { - "id": 33, - "slug": "Terrace", - "external": true, - "name": "Terrace Restaurant", - "nameshort": "Terrace", - "about": "", - "contactPhone": "1-800-541-2501", - "coordinates": { - "latitude": 42.446267, - "longitude": -76.482314 - }, - "location": "Statler", - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "10:00am", - "end": "3:00pm", - "menu": [] - } - ] - } - ], - "datesClosed": [ - "9/6/21", - "11/25/21", - "11/26/21", - "11/27/21", - "11/28/21", - "12/20/21-1/20/22" - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - }, - { - "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", - "descrshort": "Meal Plan - Debit" - } - ], - "diningItems": [ - { - "item": "Salads", - "healthy": true, - "category": "General" - }, - { - "item": "Burrito Bowl", - "healthy": false, - "category": "General" - }, - { - "item": "Burrito", - "healthy": true, - "category": "General" - }, - { - "item": "Chicken Tenders", - "healthy": false, - "category": "General" - }, - { - "item": "Fries", - "healthy": false, - "category": "General" - }, - { - "item": "Pho", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 34, - "slug": "Macs", - "external": true, - "name": "Mac's Café", - "nameshort": "Mac's", - "about": "", - "cornellDining": false, - "contactPhone": "1-800-541-2501", - "coordinates": { - "latitude": 42.445921, - "longitude": -76.481984 - }, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Statler Hotel", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "9:30am", - "end": "5:30pm", - "menu": [] - } - ] - } - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - }, - { - "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", - "descrshort": "Meal Plan - Debit" - } - ], - "datesClosed": [ - "8/18/21", - "8/19/21", - "8/20/21", - "8/21/21", - "8/22/21", - "8/23/21", - "9/6/21", - "10/9/21", - "10/10/21", - "10/11/21", - "10/12/21", - "11/24/21", - "11/25/21", - "11/26/21", - "11/27/21", - "11/28/21", - "12/16/21", - "12/17/21", - "12/20/21-1/20/22" - ], - "diningItems": [ - { - "item": "Pizza", - "healthy": false, - "category": "General" - }, - { - "item": "Pasta", - "healthy": false, - "category": "General" - }, - { - "item": "Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Sushi to Go", - "healthy": false, - "category": "General" - }, - { - "item": "Soft Drinks", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 35, - "slug": "Zeus", - "external": true, - "name": "Temple of Zeus", - "nameshort": "Temple of Zeus", - "about": "", - "cornellDining": false, - "contactPhone": "", - "coordinates": { - "latitude": 42.449091, - "longitude": -76.483414 - }, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Goldwin Smith Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "8:00am", - "end": "5:00pm", - "menu": [] - } - ] - } - ], - "datesClosed": [ - "09/06/21" - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Sandwiches", - "healthy": true, - "category": "General" - }, - { - "item": "Soups", - "healthy": true, - "category": "General" - }, - { - "item": "Baked Goods", - "healthy": true, - "category": "General" - }, - { - "item": "Candy", - "healthy": false, - "category": "General" - }, - { - "item": "Soft Drinks", - "healthy": true, - "category": "General" - } - ] - }, - { - "id": 36, - "slug": "Gimme-Coffee", - "external": true, - "name": "Gimme Coffee", - "nameshort": "Gimme Coffee", - "about": "", - "cornellDining": false, - "contactPhone": "1-607-227-5391", - "coordinates": { - "latitude": 42.444958, - "longitude": -76.481169 - }, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Gates Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "8:00am", - "end": "3:00pm", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Coffee", - "healthy": true, - "category": "General" - }, - { - "item": "Baked Goods", - "healthy": true, - "category": "General" - } - ] - }, - { - "id": 37, - "slug": "Louies-Lunch", - "external": true, - "name": "Louie's Lunch", - "nameshort": "Louie's", - "about": "", - "cornellDining": false, - "contactPhone": "1-607-257-4649", - "coordinates": { - "latitude": 42.45336, - "longitude": -76.481225 - }, - "campusArea": { - "descr": "North Campus", - "descrshort": "North" - }, - "location": "Across from Risley", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "monday-friday", - "events": [ - { - "descr": "General", - "start": "11:00am", - "end": "3:00am", - "menu": [] - } - ] - }, - { - "weekday": "saturday", - "events": [ - { - "descr": "General", - "start": "12:00pm", - "end": "3:00am", - "menu": [] - } - ] - }, - { - "weekday": "sunday", - "events": [ - { - "descr": "General", - "start": "6:00pm", - "end": "12:00am", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "French Fries", - "healthy": false, - "category": "General" - }, - { - "item": "Garlic Bread", - "healthy": false, - "category": "General" - }, - { - "item": "Cold Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Subs", - "healthy": false, - "category": "General" - }, - { - "item": "Pizza Subs", - "healthy": false, - "category": "General" - }, - { - "item": "Burgers", - "healthy": false, - "category": "General" - }, - { - "item": "Egg Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Dogs", - "healthy": false, - "category": "General" - }, - { - "item": "Wraps", - "healthy": false, - "category": "General" - }, - { - "item": "Salads", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 38, - "slug": "Anabels-Grocery", - "external": true, - "name": "Anabel's Grocery", - "nameshort": "Anabel's", - "about": "", - "cornellDining": false, - "contactPhone": "", - "coordinates": { - "latitude": 42.445061, - "longitude": -76.485826 - }, - "campusArea": { - "descr": "South Campus", - "descrshort": "South" - }, - "location": "Anabel Taylor Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "wednesday-thursday", - "events": [ - { - "descr": "General", - "start": "3:00pm", - "end": "7:00pm", - "menu": [] - } - ] - }, - { - "weekday": "friday", - "events": [ - { - "descr": "General", - "start": "12:00pm", - "end": "3:00pm", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Cornell Card", - "descrshort": "Cornell Card" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Whole Grains", - "healthy": true, - "category": "General" - }, - { - "item": "Spices", - "healthy": false, - "category": "General" - }, - { - "item": "Legumes", - "healthy": true, - "category": "General" - }, - { - "item": "Fresh and Frozen Produce", - "healthy": true, - "category": "General" - }, - { - "item": "Nuts", - "healthy": true, - "category": "General" - }, - { - "item": "Dried Fruits", - "healthy": true, - "category": "General" - }, - { - "item": "Tofu", - "healthy": true, - "category": "General" - }, - { - "item": "Eggs", - "healthy": false, - "category": "General" - }, - { - "item": "Milks", - "healthy": true, - "category": "General" - }, - { - "item": "Kombucha on Tap", - "healthy": true, - "category": "General" - }, - { - "item": "Bottled Drinks", - "healthy": false, - "category": "General" - }, - { - "item": "Bulk Items", - "healthy": false, - "category": "General" - } - ] + "eateries": [ + { + "id": 33, + "slug": "Terrace", + "external": true, + "name": "Terrace Restaurant", + "nameshort": "Terrace", + "about": "", + "contactPhone": "1-800-541-2501", + "latitude": 42.446267, + "longitude": -76.482314, + "location": "Statler", + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + } + ], + "datesClosed": [ + "9/6/21", + "11/25/21", + "11/26/21", + "11/27/21", + "11/28/21", + "12/20/21-1/20/22" + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + }, + { + "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", + "descrshort": "Meal Plan - Debit" + } + ], + "diningItems": [ + { + "item": "Salads", + "healthy": true, + "category": "General" + }, + { + "item": "Burrito Bowl", + "healthy": false, + "category": "General" + }, + { + "item": "Burrito", + "healthy": true, + "category": "General" + }, + { + "item": "Chicken Tenders", + "healthy": false, + "category": "General" + }, + { + "item": "Fries", + "healthy": false, + "category": "General" + }, + { + "item": "Pho", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 34, + "slug": "Macs", + "external": true, + "name": "Mac's Café", + "nameshort": "Mac's", + "about": "", + "cornellDining": false, + "contactPhone": "1-800-541-2501", + "latitude": 42.445921, + "longitude": -76.481984, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Statler Hotel", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + } + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + }, + { + "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", + "descrshort": "Meal Plan - Debit" + } + ], + "datesClosed": [ + "8/18/21", + "8/19/21", + "8/20/21", + "8/21/21", + "8/22/21", + "8/23/21", + "9/6/21", + "10/9/21", + "10/10/21", + "10/11/21", + "10/12/21", + "11/24/21", + "11/25/21", + "11/26/21", + "11/27/21", + "11/28/21", + "12/16/21", + "12/17/21", + "12/20/21-1/20/22" + ], + "diningItems": [ + { + "item": "Pizza", + "healthy": false, + "category": "General" + }, + { + "item": "Pasta", + "healthy": false, + "category": "General" + }, + { + "item": "Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Sushi to Go", + "healthy": false, + "category": "General" + }, + { + "item": "Soft Drinks", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 35, + "slug": "Zeus", + "external": true, + "name": "Temple of Zeus", + "nameshort": "Temple of Zeus", + "about": "", + "cornellDining": false, + "contactPhone": "", + "latitude": 42.449091, + "longitude": -76.483414, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Goldwin Smith Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + } + ], + "datesClosed": ["09/06/21"], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Sandwiches", + "healthy": true, + "category": "General" + }, + { + "item": "Soups", + "healthy": true, + "category": "General" + }, + { + "item": "Baked Goods", + "healthy": true, + "category": "General" + }, + { + "item": "Candy", + "healthy": false, + "category": "General" + }, + { + "item": "Soft Drinks", + "healthy": true, + "category": "General" + } + ] + }, + { + "id": 36, + "slug": "Gimme-Coffee", + "external": true, + "name": "Gimme Coffee", + "nameshort": "Gimme Coffee", + "about": "", + "cornellDining": false, + "contactPhone": "1-607-227-5391", + "latitude": 42.444958, + "longitude": -76.481169, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Gates Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Coffee", + "healthy": true, + "category": "General" + }, + { + "item": "Baked Goods", + "healthy": true, + "category": "General" + } + ] + }, + { + "id": 37, + "slug": "Louies-Lunch", + "external": true, + "name": "Louie's Lunch", + "nameshort": "Louie's", + "about": "", + "cornellDining": false, + "contactPhone": "1-607-257-4649", + "latitude": 42.45336, + "longitude": -76.481225, + "campusArea": { + "descr": "North Campus", + "descrshort": "North" + }, + "location": "Across from Risley", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Saturday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Sunday", + "events": [ + { + "descr": "General", + "start": "18:00", + "end": "00:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "French Fries", + "healthy": false, + "category": "General" + }, + { + "item": "Garlic Bread", + "healthy": false, + "category": "General" + }, + { + "item": "Cold Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Subs", + "healthy": false, + "category": "General" + }, + { + "item": "Pizza Subs", + "healthy": false, + "category": "General" + }, + { + "item": "Burgers", + "healthy": false, + "category": "General" + }, + { + "item": "Egg Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Dogs", + "healthy": false, + "category": "General" + }, + { + "item": "Wraps", + "healthy": false, + "category": "General" + }, + { + "item": "Salads", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 38, + "slug": "Anabels-Grocery", + "external": true, + "name": "Anabel's Grocery", + "nameshort": "Anabel's", + "about": "", + "cornellDining": false, + "contactPhone": "", + "latitude": 42.445061, + "longitude": -76.485826, + "campusArea": { + "descr": "South Campus", + "descrshort": "South" + }, + "location": "Anabel Taylor Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "19:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "19:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "19:00", + "menu": [] + } + ] + }, + { + "weekday": "Saturday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "15:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Cornell Card", + "descrshort": "Cornell Card" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Whole Grains", + "healthy": true, + "category": "General" + }, + { + "item": "Spices", + "healthy": false, + "category": "General" + }, + { + "item": "Legumes", + "healthy": true, + "category": "General" + }, + { + "item": "Fresh and Frozen Produce", + "healthy": true, + "category": "General" + }, + { + "item": "Nuts", + "healthy": true, + "category": "General" + }, + { + "item": "Dried Fruits", + "healthy": true, + "category": "General" + }, + { + "item": "Tofu", + "healthy": true, + "category": "General" + }, + { + "item": "Eggs", + "healthy": false, + "category": "General" + }, + { + "item": "Milks", + "healthy": true, + "category": "General" + }, + { + "item": "Kombucha on Tap", + "healthy": true, + "category": "General" + }, + { + "item": "Bottled Drinks", + "healthy": false, + "category": "General" + }, + { + "item": "Bulk Items", + "healthy": false, + "category": "General" } - ] + ] + } + ] } From 9a9ffecfee25001fb1bcf6280da0b4bc6107c789 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 15 Nov 2023 17:56:39 -0500 Subject: [PATCH 176/305] Revert is_cafe --- src/category/controllers/populate_category.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index d7febcc..f585c05 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -76,10 +76,10 @@ def process(self, events_dict, json_eateries): else: continue - desired_descriptions = {"Cafe", "Cart", "Coffee Shop", "Food Court"} - eatery_descriptions = {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} - is_cafe = bool(desired_descriptions.intersection(eatery_descriptions)) - + is_cafe = "Cafe" in { + eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] + } + """ For every event in an eatery --> for every menu in an eatery --> get categories """ From c0c767cd5f7d631c7d5217ed5e6cdced3fdee779 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 15 Nov 2023 15:12:19 -0800 Subject: [PATCH 177/305] add necessary files (#73) --- src/event/controllers/populate_event.py | 2 +- .../cornell_dining_now_eateries.json | 1 + src/static_sources/external_eateries.json | 828 ++++++++++++++++++ 3 files changed, 830 insertions(+), 1 deletion(-) create mode 100644 src/static_sources/cornell_dining_now_eateries.json create mode 100644 src/static_sources/external_eateries.json diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py index 5e0a78b..365d993 100644 --- a/src/event/controllers/populate_event.py +++ b/src/event/controllers/populate_event.py @@ -83,7 +83,7 @@ def process(self, json_eateries): events_dict[eatery_id] = events # create custom events for external eateries - with open("../static_sources/external_eateries.json", "r") as file: + with open("./static_sources/external_eateries.json", "r") as file: json_obj = json.load(file) for eatery in json_obj['eateries']: events_dict[eatery['id']] = self.generate_external_events(eatery) diff --git a/src/static_sources/cornell_dining_now_eateries.json b/src/static_sources/cornell_dining_now_eateries.json new file mode 100644 index 0000000..2d2ac1b --- /dev/null +++ b/src/static_sources/cornell_dining_now_eateries.json @@ -0,0 +1 @@ +{"status":"success","data":{"eateries":[{"id":31,"slug":"104-West","name":"104West!","nameshort":"104West!","about":"
\r\nLocated next to the Center for Jewish Living building on the south edge of west campus, 104West!<\/strong> is Cornell's kosher and multicultural
dining room<\/a>. Menus are prepared under the supervision of STAR-K (meat and pareve) and STAR-D (dairy) Kosher Certifications, and Jewish dietary laws are strictly followed with the direction of a resident \"Mashgiach,\" or kosher-food supervisor.\r\n

\r\nYou don't have to keep kosher to enjoy the menu\u2013come sample traditional kosher entrees and enjoy mouth-watering ethnic and international options with a kosher flair. Dining options also include Halal, Seventh-day Adventist, vegetarian, vegan, and other diets.\r\n

\r\nShabbat dinner times vary over the course of the year, based on sunset times. Save money and help us plan by making advance reservations for Shabbat dinners and holiday meals at
https:\/\/kosher.scl.cornell.edu<\/a>!\r\n

\r\nMore Info:
104West!<\/strong><\/a>\r\n

\r\n
\"104West!<\/a>\r\n

","aboutshort":"Cornell's kosher and multicultural dining room is STAR-K and STAR-D certified.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"vlpa2hk9677m9bcbh6n2dtpn7k@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-272-6907","contactEmail":null,"serviceUnitId":9,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.444266,"longitude":-76.487598,"location":"104 West Avenue","coordinates":{"latitude":42.444266,"longitude":-76.487598},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":7,"slug":"Amit-Bhatia-Libe-Cafe","name":"Amit Bhatia Libe Caf\u00e9","nameshort":"Amit Bhatia Libe Caf\u00e9","about":"
A combined effort between Cornell Dining and Olin Library, the Amit Bhatia Libe Caf\u00e9<\/strong> serves specialty Starbucks coffees, smoothies, pastries, and Freshtake Grab-n-Go sandwiches, salads, and snacks.\r\n

\r\n\r\nMore Info:
Amit Bhatia Libe Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Amit<\/a>\r\n

","aboutshort":"The perfect place to take a study break, or to enjoy a latte and a pastry while you enjoy wireless Internet access on your laptop.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"g1pfs9edl1ks5o2dbc58e7fhm8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-4344","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448019,"longitude":-76.484499,"location":"Olin Library","coordinates":{"latitude":42.448019,"longitude":-76.484499},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640124000,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640210400,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640296800,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":8,"slug":"Atrium-Cafe","name":"Atrium Caf\u00e9","nameshort":"Atrium Caf\u00e9","about":"
The Atrium Caf\u00e9's<\/strong> coffee kiosk proudly serves Starbucks specialty coffee, pastries, and Grab-n-Go items, with extended hours. When class is in session, the coffee kiosk opens weekdays at 7am, and closes at 4pm Monday through Thursday, and 2pm Friday.\r\n

\r\nMore Info:
Atrium Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Atrium<\/a>\r\n

","aboutshort":"The Atrium Caf\u00e9 is located in historic Sage Hall, home to the Johnson Graduate School of Management. Coffee kiosk is open extended hours!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"9g3c81c0p2loacsbvrjj5o371c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7591","contactEmail":null,"serviceUnitId":9999,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.445784,"longitude":-76.483232,"location":"Sage Hall","coordinates":{"latitude":42.445784,"longitude":-76.483232},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":1,"slug":"Bear-Necessities","name":"Bear Necessities Grill & C-Store","nameshort":"Bear Necessities","about":"
The grill serves up deluxe burgers, hot chicken sandwiches, delicious french fries, and other mouth-watering comfort foods. You can also order 5 Star Subs and homemade pizza. Bear Necessities<\/strong> also has a convenience store with staple food, beverages and household items.\r\n

\r\n\r\nMore Info:
Bear Necessities<\/strong><\/a>\r\n

\r\n
\"Bear<\/a>\r\n

","aboutshort":"Bear Necessities is a convenience store and grill located on the first floor of Robert Purcell Community Center on north campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h319a8fk4b5lv0644ebkskhha8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-8227","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455777,"longitude":-76.477657,"location":"RPCC - first floor","coordinates":{"latitude":42.455777,"longitude":-76.477657},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640134800,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640221200,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Convenience Store","descrshort":"convenience store"}],"diningCuisines":[{"name":"Pizza","nameshort":"Pizza","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Grill - 5-Star Subs","category":"Grill","item":"5-Star Subs","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Pizza","category":"Pasta & Pizza","item":"Pizza","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Calzones","category":"Pasta & Pizza","item":"Calzones","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Misc - Mozzarella Sticks","category":"Misc","item":"Mozzarella Sticks","healthy":false,"showCategory":false},{"descr":"Misc - Onion Petals","category":"Misc","item":"Onion Petals","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":25,"slug":"Becker-House-Dining","name":"Becker House Dining Room","nameshort":"Becker House Dining","about":"
\r\nThe Becker House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
Becker House Dining Room<\/strong><\/a>\r\n

\r\n
\"Becker<\/a>\r\n

","aboutshort":"Dining room located in Carl Becker House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"di2s9rofto7m8innt5e8vftl0o@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8882","contactEmail":null,"serviceUnitId":2,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.448336,"longitude":-76.489606,"location":"Carl Becker House","coordinates":{"latitude":42.448336,"longitude":-76.489606},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":10,"slug":"Big-Red-Barn","name":"Big Red Barn","nameshort":"Big Red Barn","about":"
\r\nThe Big Red Barn<\/strong> is a cozy caf\u00e9 with a delicious breakfast and lunch menu. It also serves as Cornell's on-campus social center for graduate and professional students. Relax at outdoor picnic tables, or sit inside by the fireplace in comfortable lounge furniture.\r\n

\r\nMore Info:
Big Red Barn<\/strong><\/a>\r\n

\r\n
\"Big<\/a>\r\n

","aboutshort":"Once a carriage house, the Big Red Barn is now a cozy caf\u00e9 with a delicious breakfast and lunch menu.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"u1kmovdep2qlmr86io8h4p3ee8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0428","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448526,"longitude":-76.48098,"location":"Big Red Barn","coordinates":{"latitude":42.448526,"longitude":-76.48098},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":11,"slug":"Bug-Stop-Bagels","name":"Bus Stop Bagels","nameshort":"Bus Stop Bagels","about":"
\r\nBus Stop Bagels<\/strong>, next to the Trillium food court in Kennedy Hall, gets fresh bagels delivered twice a day from Ithaca Bakery with all the usual toppings and a great selection of house sandwiches, plus Starbucks coffee drinks and snack foods.\r\n

\r\nMore Info:
Bus Stop Bagels<\/strong><\/a>\r\n

\r\n
\"Bus<\/a>\r\n

","aboutshort":"Bagels for breakfast, bagels for lunch, bagels to go!","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"rpp0nrlp282t9h18hhol5f0dkc@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447839,"longitude":-76.479456,"location":"Kennedy Hall","coordinates":{"latitude":42.447839,"longitude":-76.479456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640116800,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640203200,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640289600,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Coffee Bar - 96oz Coffee2Go","category":"Coffee Bar","item":"96oz Coffee2Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":12,"slug":"Cafe-Jennie","name":"Caf\u00e9 Jennie","nameshort":"Caf\u00e9 Jennie","about":"
\r\nLocated in The Cornell Store, Caf\u00e9 Jennie is named for Jennie McGraw, the daughter of John McGraw, a wealthy industrialist and a founding Cornell Trustee. Stop by for a Peet's Coffee, delicious Cheesecake Factory baked goods, and a mouth-watering array of sandwiches and wraps.\r\n

\r\nMore Info:
Caf\u00e9 Jennie<\/strong><\/a>\r\n

\r\n
\"Caf\u00e9<\/a>\r\n

","aboutshort":"A caf\u00e9 and sandwich\/pastry shop located in The Cornell Store.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"geron0aq1ooj7jugmcmdc2s2cc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8095","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446851,"longitude":-76.484376,"location":"The Cornell Store","coordinates":{"latitude":42.446851,"longitude":-76.484376},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640098800,"endTimestamp":1640120400,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640185200,"endTimestamp":1640206800,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640271600,"endTimestamp":1640286000,"start":"10:00am","end":"2:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Javiva","category":"Coffee Bar","item":"Javiva","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":2,"slug":"Carols-Cafe","name":"Carol's Caf\u00e9","nameshort":"Carol's Caf\u00e9","about":"
\r\nCome visit Carol's Caf\u00e9<\/strong> and enjoy a menu featuring specialty coffee from Starbucks, smoothies, hot daily soup selections, sushi, fresh fruit and snacks, Freshtake Grab-n-Go sandwiches and salads, delicious pastries and baked goods and more.\r\n

\r\nMore Info:
Carol's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Carol's<\/a>\r\n

","aboutshort":"Warm, inviting caf\u00e9 at Carol Tatkon Center open to the whole campus community.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"05r2nhfjbnknmsccgd6u8dij0g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2257","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453236,"longitude":-76.479404,"location":"Carol Tatkon Center, Balch Hall","coordinates":{"latitude":42.453236,"longitude":-76.479404},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":26,"slug":"Cook-House-Dining","name":"Cook House Dining Room","nameshort":"Cook House Dining","about":"
\r\nThe Cook House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. The eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Cook House Dining Room<\/strong><\/a>\r\n

\r\n
\"Cook<\/a>\r\n

","aboutshort":"Dining room located in Alice Cook House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"27hli58rto1hpf15m3sbe54sak@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-9508","contactEmail":null,"serviceUnitId":1,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.44889,"longitude":-76.488919,"location":"Alice Cook House","coordinates":{"latitude":42.44889,"longitude":-76.488919},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":14,"slug":"Cornell-Dairy-Bar","name":"Cornell Dairy Bar","nameshort":"Cornell Dairy Bar","about":"
\r\nIn the beautifully renovated Stocking Hall on the east end of Tower Road, the Cornell Dairy Bar is a great place for breakfast, coffee, or even sweet treat like Cornell ice cream, sundaes and floats. Regular hours vary seasonally, especially weekend hours, so please check the specific hours on this page for updates.\r\n

\r\nMore Info:
Cornell Dairy Bar<\/strong><\/a>\r\n

\r\n
\"Cornell<\/a>\r\n

","aboutshort":"In the renovated Stocking Hall, the Cornell Dairy features a variety of delicious Cornell Dairy ice cream, sundaes, and floats.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"prvu4v0nr4eu94mqu9q7busa6g@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7660","contactEmail":"dairybar@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447301,"longitude":-76.471398,"location":"Stocking Hall","coordinates":{"latitude":42.447301,"longitude":-76.471398},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"icecream-pink"},{"id":41,"slug":"Crossings-Cafe","name":"Crossings Caf\u00e9","nameshort":"Crossings Caf\u00e9","about":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","aboutshort":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"loadevpceh49fl3c8cuheb2dvk@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.4558,"longitude":-76.479386,"location":"Toni Morrison Hall","coordinates":{"latitude":42.4558,"longitude":-76.479386},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640124000,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640210400,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Mexican - Quesadillas","category":"Mexican","item":"Quesadillas","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Peet's Coffee","category":"Coffee Bar","item":"Peet's Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":32,"slug":"frannys","name":"Franny's","nameshort":"Franny's","about":"One of Cornell's newest eateries is Franny's, a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls. You'll find a unique mix of Pan-Asian sandwiches, rice bowls and ramen, bao buns and fries, as well as refreshing Asian-inspired drinks.","aboutshort":"Franny's is a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls, featuring a unique mix of Asian-inspired cuisine.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekdays","googleCalendarId":"cornell.edu_26ui0ai54lp0cdp2m3j27ci86c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0293","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.451053,"longitude":-76.483884,"location":"Next to Sibley Hall","coordinates":{"latitude":42.451053,"longitude":-76.483884},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[{"name":"Asian","nameshort":"Asian","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Asian - Rice Bowls","category":"Asian","item":"Rice Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Noodle Bowls","category":"Asian","item":"Noodle Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Banh Mi Sandwiches","category":"Asian","item":"Banh Mi Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":16,"slug":"Goldies-Cafe","name":"Goldie's Caf\u00e9","nameshort":"Goldie's Caf\u00e9","about":"
\r\nGoldie's is a great location on Central Campus for breakfast, lunch, or a mid-day snack. Signature sandwiches \u2013 some served on German-style pretzel rolls \u2013 have become customer favorites, and a wide array of Freshtake Grab-n-Go items, snacks, and desserts are always on the menu.\r\n

\r\nMore Info:
Goldie's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Goldie's<\/a>\r\n

","aboutshort":"Conveniently located in the Physical Sciences Building on Central Campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"kb9ce5jj2f6oli3c90tc7j6peo@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-6775","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450064,"longitude":-76.481503,"location":"Physical Sciences Building","coordinates":{"latitude":42.450064,"longitude":-76.481503},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":15,"slug":"Green-Dragon","name":"Green Dragon","nameshort":"Green Dragon","about":"
\r\nStop by the Green Dragon<\/strong> and enjoy a hot specialty Finger Lakes Coffee Roasters coffee, Freshtake Grab-n-Go sandwiches and salads, kosher items, and delicious desserts.\r\n

\r\nMore Info:
Green Dragon<\/strong><\/a>\r\n

\r\n
\"Green<\/a>\r\n

","aboutshort":"A hot spot on central campus, especially for the students, faculty, and staff of College of Architecture, Art and Planning.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"7sii70faon9ta2vpoehr69415s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3327","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450948,"longitude":-76.484456,"location":"Sibley Hall","coordinates":{"latitude":42.450948,"longitude":-76.484456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":24,"slug":"Hot-Dog-Cart","name":"Hot Dog Cart","nameshort":"Hot Dog Cart","about":"
\r\nCornell Dining's Hot Dog Cart<\/strong> is usually open outside Day Hall on summer weekdays when weather permits. On special occasions, the Hot Dog Cart may be elsewhere on campus. Keep an eye on Cornell Dining's
Facebook<\/a> and Twitter<\/a> for updates.\r\n

\r\nMore Info:
Hot Dog Cart<\/strong><\/a>\r\n

\r\n
\"Hot<\/a>\r\n

","aboutshort":"Enjoy lunch al fresco when weather permits, choosing an all-beef hot dog or vegetarian (tofu) hot dog.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"11:00am - 2:00pm weekdays, weather permitting","googleCalendarId":"cornell.edu_eaq3euadrebh0dmgqt618l7tgs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":34,"slug":"icecreamcart","name":"Ice Cream Bike","nameshort":"Ice Cream Bike","about":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","aboutshort":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekday middays for the summer, and Friday evenings for Cornell's concert series.","googleCalendarId":"cornell.edu_3kuppj4nsjes2b42jhpno5u97g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"icecream-pink"},{"id":27,"slug":"Jansens-Dining","name":"Jansen's Dining Room at Bethe House","nameshort":"Jansen's Dining","about":"
\r\nJansen's Dining Room at Bethe House<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Chef Jacob Kuehn puts together exciting new menus throughout the year. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Jansen's Dining Room at Bethe House<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Dining room located in Hans Bethe House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h0nfohf0d90ot1rmukjphj7ajc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1736","contactEmail":null,"serviceUnitId":5,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447116,"longitude":-76.48864,"location":"Hans Bethe House","coordinates":{"latitude":42.447116,"longitude":-76.48864},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":28,"slug":"Jansens-Market","name":"Jansen's Market","nameshort":"Jansen's Market","about":"
\r\nIn addition to an array of snacks, beverages, fresh and frozen take-away meals, household items, and pharmacy and beauty supplies, Jansen's Market<\/strong> also serve Starbucks coffee, bubble tea, smoothies, pastries, frozen yogurt, and Dreamfactory cheesecake.\r\n

\r\nMore Info:
Jansen's Market<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Full-service convenience store located on the first floor of Noyes Community Recreation Center on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"0dqnc6l2mt25okch8nimsnojhg@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-4997","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446325,"longitude":-76.487932,"location":"Noyes Community Recreation Center","coordinates":{"latitude":42.446325,"longitude":-76.487932},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Convenience Store","descrshort":"convenience store"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Bubble Tea","category":"Coffee Bar","item":"Bubble Tea","healthy":false,"showCategory":false},{"descr":"Misc - Peanut Butter Sandwich Bar","category":"Misc","item":"Peanut Butter Sandwich Bar","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":29,"slug":"Keeton-House-Dining","name":"Keeton House Dining Room","nameshort":"Keeton House Dining","about":"
\r\nThe Keeton House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Sample Chef Nery's unique menu offerings in the entr\u00e9e station, Asian station, grill, deli, and salad bar. This eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Keeton House Dining Room<\/strong><\/a>\r\n

\r\n
\"Keeton<\/a>\r\n

","aboutshort":"Dining room located in William Keeton House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ekd72jfc2qai617oloa2b0ibp0@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3033","contactEmail":null,"serviceUnitId":3,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446942,"longitude":-76.489992,"location":"William Keeton House","coordinates":{"latitude":42.446942,"longitude":-76.489992},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":42,"slug":"Mann-Cafe","name":"Mann Caf\u00e9","nameshort":"Mann Caf\u00e9","about":"Take a study break at Mann Caf\u00e9!","aboutshort":"Take a study break at Mann Caf\u00e9!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ppbukfcjo05629gk0t4fu26aro@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448799,"longitude":-76.47851,"location":"Mann Library on the Ag Quad","coordinates":{"latitude":42.448799,"longitude":-76.47851},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Mexican - Burritos","category":"Mexican","item":"Burritos","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":18,"slug":"Marthas-Cafe","name":"Martha's Caf\u00e9","nameshort":"Martha's Cafe","about":"
\r\nMartha's Caf\u00e9<\/strong>, in Martha Van Rensselaer Hall, offers made-to-order Mediterranean-inspired grain and salad bowls and wraps, composed bowls, Copper Horse Coffee, and breakfast!\r\n

\r\nMore Info:
Martha's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Martha's<\/a>\r\n

","aboutshort":"Fresh food with a Mediterranean flair.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sperf092mrbt796rr36toeqrus@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-8080","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450115,"longitude":-76.479237,"location":"Martha Van Rensselaer Hall","coordinates":{"latitude":42.450115,"longitude":-76.479237},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Milk","category":"Cornell Dairy","item":"Milk","healthy":false,"showCategory":true},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false},{"descr":"Lunch - Hot and Cold Mediterranean Bowls","category":"Lunch","item":"Hot and Cold Mediterranean Bowls","healthy":true,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":19,"slug":"Mattins-Cafe","name":"Mattin's Caf\u00e9","nameshort":"Mattin's Caf\u00e9","about":"
\r\nYou\u2019ll find Mattin's Caf\u00e9<\/strong> in the atrium of Duffield Hall, adjacent to Phillips Hall on the Engineering Quad. Enjoy made-to-order deli sandwiches, boneless wings, hot Starbucks coffee, Freshtake Grab-n-Go items, soups, kosher items, mouth-watering pastries and more.\r\n

\r\nMore Info:
Mattin's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Mattin's<\/a>\r\n

","aboutshort":"Popular with engineers and a go to spot in the stunning atrium of Duffield Hall.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"1qman2n728pqjuq5ntaoofc7v0@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-4581","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.444162,"longitude":-76.482287,"location":"Duffield Hall","coordinates":{"latitude":42.444162,"longitude":-76.482287},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640113200,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640199600,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":33,"slug":"mccormicks","name":"McCormick's at Moakley House","nameshort":"McCormick's","about":"McCormick's at Moakley House offers a casual environment close to campus, open daily through the season. Treat a campus visitor to an impeccably served luncheon, stop for refreshments after a round of golf, meet coworkers at the end of the day for a burger and a beer, or bring the family for a relaxing weekend brunch.\r\n\r\nNamed for Jack McCormick, a varsity golfer from the Cornell Class of 1957 whose will included a bequest to modernize Moakley House, this eatery alongside the award-winning Robert Trent Jones Golf Course at Cornell University isn't just for golfers. It's open to the public and has plenty of parking, and we welcome everyone for a drink, a snack, or a meal.\r\n\r\nMcCormick's is closed for the 2021 season as of September 24th.\r\n\r\n

\r\nMore Info:
McCormick's at Moakley House<\/strong><\/a>\r\n

","aboutshort":"Named for Jack McCormick, a varsity golfer from the Cornell Class of 1957, McCormick's is open to the public and has plenty of parking.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"cornell.edu_gqlmrg7n0qk8qihl75ru2u1p80@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-6536","contactEmail":"mccormicks@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.458324,"longitude":-76.469539,"location":"Robert Trent Jones Golf Course","coordinates":{"latitude":42.458324,"longitude":-76.469539},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false}],"announcements":[{"id":199,"title":"McCormick's is closed for the 2021 season after Friday, September 24th.","announceType":"EATERY","startTimestamp":1632283200,"stopTimestamp":1640408340}],"icon":"coffee-brown"},{"id":3,"slug":"North-Star","name":"North Star Dining Room","nameshort":"North Star","about":"
\r\nThe North Star Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Enjoy an open kitchen concept and numerous unique food stations serving a huge variety of ethnic and regional American cuisine. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
North Star Dining Room<\/strong><\/a>\r\n

\r\n
\"North<\/a>\r\n

","aboutshort":"Dining room located in Appel Commons on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ecbhqf3ibeei09dds91viod5g8@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2992","contactEmail":null,"serviceUnitId":7,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453527,"longitude":-76.475944,"location":"Appel Commons, Third floor","coordinates":{"latitude":42.453527,"longitude":-76.475944},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[{"id":216,"title":"North Star Dining Room is closed for renovation through the Spring 2022 semester!","announceType":"EATERY","startTimestamp":1639976400,"stopTimestamp":1654055940}],"icon":"restaurant-red"},{"id":20,"slug":"Okenshields","name":"Okenshields","nameshort":"Okenshields","about":"
\r\nOkenshields<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. With hundreds of menu options to choose from \u2013 from an Asian station to a healthy foods bar featuring whole grain salads and cut fruit \u2013 you're sure to find something that meets your fancy.\r\n

\r\nMore Info:
Okenshields<\/strong><\/a>\r\n

\r\n
\"Okenshields<\/a>\r\n

","aboutshort":"Dining room located in Willard Straight Hall on Central Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"3hku0mr66kapq1lh8fakug9kko@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6636","contactEmail":null,"serviceUnitId":10,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446491,"longitude":-76.485678,"location":"Willard Straight Hall","coordinates":{"latitude":42.446491,"longitude":-76.485678},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":4,"slug":"Risley-Dining","name":"Risley Dining Room","nameshort":"Risley Dining Room","about":"
\r\nRisley Dining Room<\/strong> is a 100% gluten-free, tree-nut-free, and peanut-free
dining room<\/a> for house residents, and for the entire Cornell community. There are always vegan and vegetarian choices. Modeled after the Christchurch Refectory at Oxford University, the dining room maintains the same Gothic charm as when it first opened as the all-Ivy \"Risley Great Hall\" in 1913. \r\n

\r\nMore Info:
Risley Dining Room<\/strong><\/a>\r\n

\r\n
\"Risley<\/a>\r\n

","aboutshort":"Dining room located in Risley Residential College on North Campus.","nutrition":"Risley Dining Room is certified 100% gluten-free, tree-nut-free, and peanut-free. Thank you for not bringing outside food into Risley!<\/strong>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"hq98btd396f3077p88d30c84fs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-4229","contactEmail":null,"serviceUnitId":8,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453117,"longitude":-76.481946,"location":"Risley Residential College","coordinates":{"latitude":42.453117,"longitude":-76.481946},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":5,"slug":"RPCC-Marketplace","name":"Robert Purcell Marketplace Eatery","nameshort":"RPCC Marketplace","about":"
\r\nThe Robert Purcell Marketplace Eatery<\/strong> is an award-winning
dining room<\/a> with a huge variety of menu options. Chef Kevin Moore, who has been with Cornell Dining for over 25 years, most recently at 104West!<\/a>, plans creative menus with roots in a variety of international traditions. Note: Our Late Dinner hours feature a limited menu.\r\n

\r\nMore Info:
Robert Purcell Marketplace Eatery<\/strong><\/a>\r\n

\r\n
\"Robert<\/a>\r\n

","aboutshort":"Dining room located in Robert Purcell Community Center on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"32uglqeiqfo9edhpp4tka8oqsc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1138","contactEmail":null,"serviceUnitId":6,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455973,"longitude":-76.477354,"location":"RPCC, Third floor","coordinates":{"latitude":42.455973,"longitude":-76.477354},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640088000,"endTimestamp":1640100600,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":2,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Pork Breakfast Sausage","healthy":false,"sortIdx":3},{"item":"Home Fries","healthy":true,"sortIdx":4},{"item":"Pancakes with Syrup","healthy":false,"sortIdx":5},{"item":"French Toast Sticks","healthy":false,"sortIdx":6},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":7}]},{"category":"Grill Station","sortIdx":3,"items":[{"item":"Scrambled Tofu","healthy":true,"sortIdx":1},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":2}]},{"category":"Specialty Station","sortIdx":4,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":2},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":3},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":4},{"item":"Waffle Bar","healthy":false,"sortIdx":5},{"item":"Assorted Cereal","healthy":false,"sortIdx":6},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":7}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640100600,"endTimestamp":1640115000,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":5,"items":[{"item":"Beef Vegetable Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":6,"items":[{"item":"Salad Bar","healthy":true,"sortIdx":1},{"item":"Grains For Brains","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":7,"items":[{"item":"Pasta Primavera","healthy":false,"sortIdx":1},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":8,"items":[{"item":"Potato Tots","healthy":false,"sortIdx":1},{"item":"Calabacitas","healthy":true,"sortIdx":2},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":9,"items":[{"item":"Hacienda Cheese Quesadilla","healthy":false,"sortIdx":1},{"item":"Grilled Cheese Sandwich","healthy":false,"sortIdx":2}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640124000,"endTimestamp":1640133000,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":10,"items":[{"item":"Cornell Chicken Noodle Soup","healthy":false,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":11,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2},{"item":"Grains For Brains","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":12,"items":[{"item":"Salmon","healthy":false,"sortIdx":1},{"item":"Spanish Style Brown Rice & Beans","healthy":true,"sortIdx":2},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Sides","sortIdx":13,"items":[{"item":"Steamed Vegetable Melange","healthy":true,"sortIdx":1},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":2},{"item":"Sauteed Zucchini","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4},{"item":"Fried Potato Puffs","healthy":false,"sortIdx":5}]},{"category":"Grill Station","sortIdx":14,"items":[{"item":"BBQ Chicken Pizza","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640174400,"endTimestamp":1640187000,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":15,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Turkey Breakfast Sausage","healthy":true,"sortIdx":3},{"item":"Bacon","healthy":false,"sortIdx":4},{"item":"Steamed Brown Rice","healthy":true,"sortIdx":5},{"item":"Home Fries","healthy":true,"sortIdx":6}]},{"category":"Grill Station","sortIdx":16,"items":[{"item":"Bacon & Cheese Omelet","healthy":false,"sortIdx":1},{"item":"Steamed Fresh Vegetables","healthy":true,"sortIdx":2},{"item":"Western Scrambled Tofu","healthy":true,"sortIdx":3},{"item":"Build Your Own Omelette","healthy":true,"sortIdx":4},{"item":"Cheese Omelet","healthy":false,"sortIdx":5}]},{"category":"Specialty Station","sortIdx":17,"items":[{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":1},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":2},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":3},{"item":"Waffle Bar","healthy":false,"sortIdx":4},{"item":"Assorted Cereal","healthy":false,"sortIdx":5},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":6}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640187000,"endTimestamp":1640201400,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":18,"items":[{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":19,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Salad Bar","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":20,"items":[{"item":"Pasta with Sun-dried Tomato Basil & Feta","healthy":true,"sortIdx":1},{"item":"Kale Pesto Chicken","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":21,"items":[{"item":"Chef's Choice Seasonal Vegetable","healthy":true,"sortIdx":1},{"item":"Seared Kale","healthy":true,"sortIdx":2},{"item":"Rosemary Roasted Red Skin Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":22,"items":[{"item":"Grilled Chicken Breast","healthy":true,"sortIdx":1},{"item":"Grilled Hot Dogs","healthy":false,"sortIdx":2},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4}]},{"category":"Pizza Station","sortIdx":23,"items":[{"item":"Supreme Vegetable Pizza","healthy":false,"sortIdx":1},{"item":"White Garlic Hawaiian Pizza","healthy":false,"sortIdx":2}]},{"category":"Wok\/Asian Station","sortIdx":24,"items":[{"item":"Moo Goo Gai Pan","healthy":false,"sortIdx":1},{"item":"Vegetable Fried Rice","healthy":false,"sortIdx":2},{"item":"Long Grain Brown Rice","healthy":true,"sortIdx":3},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":4}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640210400,"endTimestamp":1640219400,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":25,"items":[{"item":"Cornell Cream of Mushroom Soup","healthy":false,"sortIdx":1},{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":2}]},{"category":"Salad Bar Station","sortIdx":26,"items":[{"item":"Grains For Brains","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Entrees","sortIdx":27,"items":[{"item":"Roasted Eggplant & Zucchini Casserole","healthy":false,"sortIdx":1},{"item":"Chef's Choice Vegan Entree","healthy":false,"sortIdx":2},{"item":"Chef's Choice Meat Entree","healthy":false,"sortIdx":3},{"item":"Roasted Pork Loin with Mango Mojo","healthy":true,"sortIdx":4},{"item":"Gourmet Pretzel Bar","healthy":false,"sortIdx":5}]},{"category":"Hot Traditional Station - Sides","sortIdx":28,"items":[{"item":"Steamed Winter Vegetables","healthy":true,"sortIdx":1},{"item":"Sauteed Super Greens","healthy":true,"sortIdx":2},{"item":"Roasted Garlic & Herb Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":29,"items":[{"item":"Veggie Burger","healthy":true,"sortIdx":1},{"item":"Char-Grilled Hamburgers","healthy":false,"sortIdx":2},{"item":"Build Your Own Nachos","healthy":false,"sortIdx":3}]},{"category":"Pasta and Pizza Station","sortIdx":30,"items":[{"item":"Pasta By Design","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2},{"item":"Broccoli Alfredo Pizza","healthy":false,"sortIdx":3},{"item":"Buffalo Chicken Pizza","healthy":false,"sortIdx":4}]},{"category":"Wok\/Asian Station","sortIdx":31,"items":[{"item":"Pancake Bar with Fruit Toppings & Syrups","healthy":false,"sortIdx":1},{"item":"Omelet Bar","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640260800,"endTimestamp":1640273400,"start":"7:00am","end":"10:30am","menu":[],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640273400,"endTimestamp":1640287800,"start":"10:30am","end":"2:30pm","menu":[],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640296800,"endTimestamp":1640305800,"start":"5:00pm","end":"7:30pm","menu":[],"calSummary":"Dinner"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":30,"slug":"Rose-House-Dining","name":"Rose House Dining Room","nameshort":"Rose House Dining","about":"
\r\nThe Rose House Dining Room<\/strong> is a
dning room<\/a> for house residents, and for the entire Cornell community. This eatery features traditional hot entrees, an Asian station, a grill, a deli, and a salad bar. Continental breakfast on Saturday and Sunday. Sample Chef Matt Seeber's daily menu options \u2013 you won't be disappointed!\r\n

\r\nMore Info:
Rose House Dining Room<\/strong><\/a>\r\n

\r\n
\"Rose<\/a>\r\n

","aboutshort":"Dining room located in Flora Rose House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"mo4mqfpe88ucqaer728ovfei18@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-0337","contactEmail":null,"serviceUnitId":4,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447813,"longitude":-76.488791,"location":"Flora Rose House","coordinates":{"latitude":42.447813,"longitude":-76.488791},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":21,"slug":"Rustys","name":"Rusty's","nameshort":"Rusty's","about":"
\r\nConveniently located in the lobby of Uris Hall, Rusty's<\/strong> offers Starbucks specialty coffees, Straight from the Oven baked goods, Freshtake Grab-n-Go sandwiches, salads and more. At any time of day, you're sure to find something to whet your appetite!\r\n

\r\nMore Info:
Rusty's<\/strong><\/a>\r\n

\r\n
\"Rusty's<\/a>\r\n

","aboutshort":"A great place to grab a quick coffee or a bite to eat on your way to class or work.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sqp9nd9rt727fm7v2sgmfelkps@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6656","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447399,"longitude":-76.482302,"location":"Uris Hall","coordinates":{"latitude":42.447399,"longitude":-76.482302},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":13,"slug":"StraightMarket","name":"Straight from the Market","nameshort":"Straight from the Market","about":"With a farm-fresh marketplace flair, Straight from the Market offers a wide variety of fresh and marinated vegetables, hummus and tapenade bar, salad fixings, and Cornell Dairy ice cream on the main floor of Willard Straight Hall adjacent to the Straight Terrace. Healthy, flavorful, and ready-to-go meat, vegan, and vegetarian choices daily.","aboutshort":"A farm-fresh marketplace on the main floor of the Straight.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ju94n6trv0ccoqcnd5u7otle50@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3963","contactEmail":null,"serviceUnitId":11,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446372,"longitude":-76.485786,"location":"Willard Straight Hall","coordinates":{"latitude":42.446372,"longitude":-76.485786},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":23,"slug":"Trillium","name":"Trillium","nameshort":"Trillium","about":"
\r\nAt Trillium<\/strong> choose from a wide variety of food stations serving Mexican food, Asian dishes, sumptuous burgers, pasta bar, sandwiches, salads, soups, and made-to-order omelets, among many other delectable menu options.\r\n

\r\nMore Info:
Trillium<\/strong><\/a>\r\n

\r\n
\"Trillium<\/a>\r\n

","aboutshort":"Located in Kennedy Hall in the heart of Central Campus, Trillium is one of our most popular food courts.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"i8v43jd76mugc62voucp4dqn9s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447883,"longitude":-76.479127,"location":"Kennedy Hall","coordinates":{"latitude":42.447883,"longitude":-76.479127},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Food Court","descrshort":"food court"}],"diningCuisines":[{"name":"Mexican","nameshort":"Mexican","descr":""},{"name":"Asian","nameshort":"Asian","descr":""},{"name":"Pizza","nameshort":"Pizza","descr":""},{"name":"Italian","nameshort":"Italian","descr":null}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"fastfood-blue"}]},"message":null,"meta":{"copyright":"Cornell University, Cornell Dining","responseDttm":"2021-12-22T11:15:18-0500"}} \ No newline at end of file diff --git a/src/static_sources/external_eateries.json b/src/static_sources/external_eateries.json new file mode 100644 index 0000000..c927f6a --- /dev/null +++ b/src/static_sources/external_eateries.json @@ -0,0 +1,828 @@ +{ + "eateries": [ + { + "id": 33, + "slug": "Terrace", + "external": true, + "name": "Terrace Restaurant", + "nameshort": "Terrace", + "about": "", + "contactPhone": "1-800-541-2501", + "latitude": 42.446267, + "longitude": -76.482314, + "location": "Statler", + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + } + ], + "datesClosed": [ + "9/6/21", + "11/25/21", + "11/26/21", + "11/27/21", + "11/28/21", + "12/20/21-1/20/22" + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + }, + { + "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", + "descrshort": "Meal Plan - Debit" + } + ], + "diningItems": [ + { + "item": "Salads", + "healthy": true, + "category": "General" + }, + { + "item": "Burrito Bowl", + "healthy": false, + "category": "General" + }, + { + "item": "Burrito", + "healthy": true, + "category": "General" + }, + { + "item": "Chicken Tenders", + "healthy": false, + "category": "General" + }, + { + "item": "Fries", + "healthy": false, + "category": "General" + }, + { + "item": "Pho", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 34, + "slug": "Macs", + "external": true, + "name": "Mac's Café", + "nameshort": "Mac's", + "about": "", + "cornellDining": false, + "contactPhone": "1-800-541-2501", + "latitude": 42.445921, + "longitude": -76.481984, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Statler Hotel", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + } + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + }, + { + "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", + "descrshort": "Meal Plan - Debit" + } + ], + "datesClosed": [ + "8/18/21", + "8/19/21", + "8/20/21", + "8/21/21", + "8/22/21", + "8/23/21", + "9/6/21", + "10/9/21", + "10/10/21", + "10/11/21", + "10/12/21", + "11/24/21", + "11/25/21", + "11/26/21", + "11/27/21", + "11/28/21", + "12/16/21", + "12/17/21", + "12/20/21-1/20/22" + ], + "diningItems": [ + { + "item": "Pizza", + "healthy": false, + "category": "General" + }, + { + "item": "Pasta", + "healthy": false, + "category": "General" + }, + { + "item": "Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Sushi to Go", + "healthy": false, + "category": "General" + }, + { + "item": "Soft Drinks", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 35, + "slug": "Zeus", + "external": true, + "name": "Temple of Zeus", + "nameshort": "Temple of Zeus", + "about": "", + "cornellDining": false, + "contactPhone": "", + "latitude": 42.449091, + "longitude": -76.483414, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Goldwin Smith Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + } + ], + "datesClosed": ["09/06/21"], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Sandwiches", + "healthy": true, + "category": "General" + }, + { + "item": "Soups", + "healthy": true, + "category": "General" + }, + { + "item": "Baked Goods", + "healthy": true, + "category": "General" + }, + { + "item": "Candy", + "healthy": false, + "category": "General" + }, + { + "item": "Soft Drinks", + "healthy": true, + "category": "General" + } + ] + }, + { + "id": 36, + "slug": "Gimme-Coffee", + "external": true, + "name": "Gimme Coffee", + "nameshort": "Gimme Coffee", + "about": "", + "cornellDining": false, + "contactPhone": "1-607-227-5391", + "latitude": 42.444958, + "longitude": -76.481169, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Gates Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Coffee", + "healthy": true, + "category": "General" + }, + { + "item": "Baked Goods", + "healthy": true, + "category": "General" + } + ] + }, + { + "id": 37, + "slug": "Louies-Lunch", + "external": true, + "name": "Louie's Lunch", + "nameshort": "Louie's", + "about": "", + "cornellDining": false, + "contactPhone": "1-607-257-4649", + "latitude": 42.45336, + "longitude": -76.481225, + "campusArea": { + "descr": "North Campus", + "descrshort": "North" + }, + "location": "Across from Risley", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Saturday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Sunday", + "events": [ + { + "descr": "General", + "start": "18:00", + "end": "00:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "French Fries", + "healthy": false, + "category": "General" + }, + { + "item": "Garlic Bread", + "healthy": false, + "category": "General" + }, + { + "item": "Cold Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Subs", + "healthy": false, + "category": "General" + }, + { + "item": "Pizza Subs", + "healthy": false, + "category": "General" + }, + { + "item": "Burgers", + "healthy": false, + "category": "General" + }, + { + "item": "Egg Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Dogs", + "healthy": false, + "category": "General" + }, + { + "item": "Wraps", + "healthy": false, + "category": "General" + }, + { + "item": "Salads", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 38, + "slug": "Anabels-Grocery", + "external": true, + "name": "Anabel's Grocery", + "nameshort": "Anabel's", + "about": "", + "cornellDining": false, + "contactPhone": "", + "latitude": 42.445061, + "longitude": -76.485826, + "campusArea": { + "descr": "South Campus", + "descrshort": "South" + }, + "location": "Anabel Taylor Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "19:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "19:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "19:00", + "menu": [] + } + ] + }, + { + "weekday": "Saturday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "15:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Cornell Card", + "descrshort": "Cornell Card" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Whole Grains", + "healthy": true, + "category": "General" + }, + { + "item": "Spices", + "healthy": false, + "category": "General" + }, + { + "item": "Legumes", + "healthy": true, + "category": "General" + }, + { + "item": "Fresh and Frozen Produce", + "healthy": true, + "category": "General" + }, + { + "item": "Nuts", + "healthy": true, + "category": "General" + }, + { + "item": "Dried Fruits", + "healthy": true, + "category": "General" + }, + { + "item": "Tofu", + "healthy": true, + "category": "General" + }, + { + "item": "Eggs", + "healthy": false, + "category": "General" + }, + { + "item": "Milks", + "healthy": true, + "category": "General" + }, + { + "item": "Kombucha on Tap", + "healthy": true, + "category": "General" + }, + { + "item": "Bottled Drinks", + "healthy": false, + "category": "General" + }, + { + "item": "Bulk Items", + "healthy": false, + "category": "General" + } + ] + } + ] +} From ad63fe30ecd3753db5f7e85df5723ac4aed408d5 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 15 Nov 2023 15:27:40 -0800 Subject: [PATCH 178/305] Remove cdn folder to make things more clear (#71) * remove cdn folder to make things more clear * fix reports getting deleted * Resolve empty menus issue for cafes * revert is_cafe breaking change (#72) * implement adding times for external eateries (#70) * add necessary files (#73) * remove unnecessary files --------- Co-authored-by: Thomas Vignos --- src/cdn_parser/__init__.py | 0 src/cdn_parser/admin.py | 3 - src/cdn_parser/apps.py | 6 - src/cdn_parser/controllers/filter_models.py | 0 src/cdn_parser/controllers/populate_models.py | 69 -- src/cdn_parser/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/populate_models.py | 13 - src/cdn_parser/migrations/__init__.py | 0 src/cdn_parser/tests.py | 3 - src/cdn_parser/views.py | 3 - src/eatery/models.py | 6 - .../management/commands/populate_models.py | 69 ++ src/eatery_blue_backend/settings.py | 4 +- src/eatery_blue_backend/urls.py | 1 - .../cornell_dining_now_eateries.json | 1 - static_sources/external_eateries.json | 828 ------------------ 17 files changed, 71 insertions(+), 935 deletions(-) delete mode 100644 src/cdn_parser/__init__.py delete mode 100644 src/cdn_parser/admin.py delete mode 100644 src/cdn_parser/apps.py delete mode 100644 src/cdn_parser/controllers/filter_models.py delete mode 100644 src/cdn_parser/controllers/populate_models.py delete mode 100644 src/cdn_parser/management/__init__.py delete mode 100644 src/cdn_parser/management/commands/__init__.py delete mode 100644 src/cdn_parser/management/commands/populate_models.py delete mode 100644 src/cdn_parser/migrations/__init__.py delete mode 100644 src/cdn_parser/tests.py delete mode 100644 src/cdn_parser/views.py create mode 100644 src/eatery_blue_backend/management/commands/populate_models.py delete mode 100644 static_sources/cornell_dining_now_eateries.json delete mode 100644 static_sources/external_eateries.json diff --git a/src/cdn_parser/__init__.py b/src/cdn_parser/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/cdn_parser/admin.py b/src/cdn_parser/admin.py deleted file mode 100644 index 8c38f3f..0000000 --- a/src/cdn_parser/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/src/cdn_parser/apps.py b/src/cdn_parser/apps.py deleted file mode 100644 index 4558e63..0000000 --- a/src/cdn_parser/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class CdnParserConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'cdn_parser' diff --git a/src/cdn_parser/controllers/filter_models.py b/src/cdn_parser/controllers/filter_models.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/cdn_parser/controllers/populate_models.py b/src/cdn_parser/controllers/populate_models.py deleted file mode 100644 index f1abc5c..0000000 --- a/src/cdn_parser/controllers/populate_models.py +++ /dev/null @@ -1,69 +0,0 @@ -import requests -from eatery.util.constants import CORNELL_DINING_URL, dining_id_to_internal_id -from django.core import management -from event.models import Event -from eatery.models import Eatery -from eatery.controllers.populate_eatery import PopulateEateryController -from event.controllers.populate_event import PopulateEventController -from item.controllers.populate_item import PopulateItemController -from category.controllers.populate_category import PopulateCategoryController -""" -Parse through CornellDiningNow json to populate our eatery models. -""" - -class CornellDiningNowController(): - def __init__(self): - self = self - - def get_json(self): - try: - response = requests.get(CORNELL_DINING_URL) - except Exception as e: - raise e - if response.status_code <= 400: - response = response.json() - json_eateries = response["data"]["eateries"] - return json_eateries - - def process(self): - """ - 1. Get JSON from API - - 2. create eateries (fron CDN json) - - 3. create events (from CDN json) - return events_dict = { eatery_id : [event, event, event...], eatery_id : ... } - - 4. create menus for every eatery's events - return menus_dict = { eatery_id : [menu, menu, menu...] } - - 5. create categories in each menu - return categories_dict = - { eatery_id : - { menu[i] : {"category_name" : id, "category_name" : id...}, - menu[i] : {"category_name" : id...} - } - } - - 6. create items for each category - - """ - - json_eateries = self.get_json() - - Event.truncate() - Eatery.truncate() - - print("Populating eateries") - PopulateEateryController().process(json_eateries) - - print("Populating events") - events_dict = PopulateEventController().process(json_eateries) - - print("Populating categories") - categories_dict = PopulateCategoryController().process(events_dict, json_eateries) - - print("Populating items") - PopulateItemController().process(categories_dict, json_eateries) - - print("Done populating") diff --git a/src/cdn_parser/management/__init__.py b/src/cdn_parser/management/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/cdn_parser/management/commands/__init__.py b/src/cdn_parser/management/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/cdn_parser/management/commands/populate_models.py b/src/cdn_parser/management/commands/populate_models.py deleted file mode 100644 index 3042f74..0000000 --- a/src/cdn_parser/management/commands/populate_models.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.core.management.base import BaseCommand -from django.utils import timezone -from cdn_parser.controllers.populate_models import CornellDiningNowController -from datetime import datetime - -class Command(BaseCommand): - help = 'Populates all models' - def handle(self, *args, **kwargs): - self.stdout.write(f"Populating models at {datetime.now()} UTC") - start = int(datetime.now().timestamp()) - CornellDiningNowController().process() - self.stdout.write(f"Populated models ({int(datetime.now().timestamp()) - start}s)") - diff --git a/src/cdn_parser/migrations/__init__.py b/src/cdn_parser/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/cdn_parser/tests.py b/src/cdn_parser/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/cdn_parser/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/cdn_parser/views.py b/src/cdn_parser/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/src/cdn_parser/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/src/eatery/models.py b/src/eatery/models.py index e515788..48f2eb8 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -24,9 +24,3 @@ class CampusArea(models.TextChoices): payment_accepts_meal_swipes = models.BooleanField(null=True, blank=True) payment_accepts_brbs = models.BooleanField(null=True, blank=True) payment_accepts_cash = models.BooleanField(null=True, blank=True) - - - @classmethod - def truncate(cls): - with connection.cursor() as cursor: - cursor.execute('TRUNCATE TABLE {} CASCADE'.format(cls._meta.db_table)) diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py new file mode 100644 index 0000000..afd0248 --- /dev/null +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -0,0 +1,69 @@ +from django.core.management.base import BaseCommand +from datetime import datetime +import requests +from eatery.util.constants import CORNELL_DINING_URL +from event.models import Event +from eatery.controllers.populate_eatery import PopulateEateryController +from event.controllers.populate_event import PopulateEventController +from item.controllers.populate_item import PopulateItemController +from category.controllers.populate_category import PopulateCategoryController + +class Command(BaseCommand): + help = 'Populates all models' + def handle(self, *args, **kwargs): + self.stdout.write(f"Populating models at {datetime.now()} UTC") + start = int(datetime.now().timestamp()) + self.process() + self.stdout.write(f"Populated models ({int(datetime.now().timestamp()) - start}s)") + + def get_json(self): + try: + response = requests.get(CORNELL_DINING_URL) + except Exception as e: + raise e + if response.status_code <= 400: + response = response.json() + json_eateries = response["data"]["eateries"] + return json_eateries + + def process(self): + """ + 1. Get JSON from API + + 2. create eateries (fron CDN json) + + 3. create events (from CDN json) + return events_dict = { eatery_id : [event, event, event...], eatery_id : ... } + + 4. create menus for every eatery's events + return menus_dict = { eatery_id : [menu, menu, menu...] } + + 5. create categories in each menu + return categories_dict = + { eatery_id : + { menu[i] : {"category_name" : id, "category_name" : id...}, + menu[i] : {"category_name" : id...} + } + } + + 6. create items for each category + + """ + + json_eateries = self.get_json() + + Event.truncate() + + print("Populating eateries") + PopulateEateryController().process(json_eateries) + + print("Populating events") + events_dict = PopulateEventController().process(json_eateries) + + print("Populating categories") + categories_dict = PopulateCategoryController().process(events_dict, json_eateries) + + print("Populating items") + PopulateItemController().process(categories_dict, json_eateries) + + print("Done populating") \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 01396e0..23fb2ab 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -39,14 +39,14 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework.authtoken", + "rest_framework", + "eatery_blue_backend", "eatery", "alert", "event", "report", "item", "category", - "cdn_parser", - "rest_framework", "person", ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 3dee7a6..2909e69 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -22,5 +22,4 @@ path("report/", include("report.urls")), path("alert/", include("alert.urls")), path("person/", include("person.urls")), - #path("wait_time/", include("wait_time.urls")) ] diff --git a/static_sources/cornell_dining_now_eateries.json b/static_sources/cornell_dining_now_eateries.json deleted file mode 100644 index 2d2ac1b..0000000 --- a/static_sources/cornell_dining_now_eateries.json +++ /dev/null @@ -1 +0,0 @@ -{"status":"success","data":{"eateries":[{"id":31,"slug":"104-West","name":"104West!","nameshort":"104West!","about":"
\r\nLocated next to the Center for Jewish Living building on the south edge of west campus, 104West!<\/strong> is Cornell's kosher and multicultural
dining room<\/a>. Menus are prepared under the supervision of STAR-K (meat and pareve) and STAR-D (dairy) Kosher Certifications, and Jewish dietary laws are strictly followed with the direction of a resident \"Mashgiach,\" or kosher-food supervisor.\r\n

\r\nYou don't have to keep kosher to enjoy the menu\u2013come sample traditional kosher entrees and enjoy mouth-watering ethnic and international options with a kosher flair. Dining options also include Halal, Seventh-day Adventist, vegetarian, vegan, and other diets.\r\n

\r\nShabbat dinner times vary over the course of the year, based on sunset times. Save money and help us plan by making advance reservations for Shabbat dinners and holiday meals at
https:\/\/kosher.scl.cornell.edu<\/a>!\r\n

\r\nMore Info:
104West!<\/strong><\/a>\r\n

\r\n
\"104West!<\/a>\r\n

","aboutshort":"Cornell's kosher and multicultural dining room is STAR-K and STAR-D certified.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"vlpa2hk9677m9bcbh6n2dtpn7k@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-272-6907","contactEmail":null,"serviceUnitId":9,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.444266,"longitude":-76.487598,"location":"104 West Avenue","coordinates":{"latitude":42.444266,"longitude":-76.487598},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":7,"slug":"Amit-Bhatia-Libe-Cafe","name":"Amit Bhatia Libe Caf\u00e9","nameshort":"Amit Bhatia Libe Caf\u00e9","about":"
A combined effort between Cornell Dining and Olin Library, the Amit Bhatia Libe Caf\u00e9<\/strong> serves specialty Starbucks coffees, smoothies, pastries, and Freshtake Grab-n-Go sandwiches, salads, and snacks.\r\n

\r\n\r\nMore Info:
Amit Bhatia Libe Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Amit<\/a>\r\n

","aboutshort":"The perfect place to take a study break, or to enjoy a latte and a pastry while you enjoy wireless Internet access on your laptop.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"g1pfs9edl1ks5o2dbc58e7fhm8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-4344","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448019,"longitude":-76.484499,"location":"Olin Library","coordinates":{"latitude":42.448019,"longitude":-76.484499},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640124000,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640210400,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640296800,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":8,"slug":"Atrium-Cafe","name":"Atrium Caf\u00e9","nameshort":"Atrium Caf\u00e9","about":"
The Atrium Caf\u00e9's<\/strong> coffee kiosk proudly serves Starbucks specialty coffee, pastries, and Grab-n-Go items, with extended hours. When class is in session, the coffee kiosk opens weekdays at 7am, and closes at 4pm Monday through Thursday, and 2pm Friday.\r\n

\r\nMore Info:
Atrium Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Atrium<\/a>\r\n

","aboutshort":"The Atrium Caf\u00e9 is located in historic Sage Hall, home to the Johnson Graduate School of Management. Coffee kiosk is open extended hours!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"9g3c81c0p2loacsbvrjj5o371c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7591","contactEmail":null,"serviceUnitId":9999,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.445784,"longitude":-76.483232,"location":"Sage Hall","coordinates":{"latitude":42.445784,"longitude":-76.483232},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":1,"slug":"Bear-Necessities","name":"Bear Necessities Grill & C-Store","nameshort":"Bear Necessities","about":"
The grill serves up deluxe burgers, hot chicken sandwiches, delicious french fries, and other mouth-watering comfort foods. You can also order 5 Star Subs and homemade pizza. Bear Necessities<\/strong> also has a convenience store with staple food, beverages and household items.\r\n

\r\n\r\nMore Info:
Bear Necessities<\/strong><\/a>\r\n

\r\n
\"Bear<\/a>\r\n

","aboutshort":"Bear Necessities is a convenience store and grill located on the first floor of Robert Purcell Community Center on north campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h319a8fk4b5lv0644ebkskhha8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-8227","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455777,"longitude":-76.477657,"location":"RPCC - first floor","coordinates":{"latitude":42.455777,"longitude":-76.477657},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640134800,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640221200,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Convenience Store","descrshort":"convenience store"}],"diningCuisines":[{"name":"Pizza","nameshort":"Pizza","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Grill - 5-Star Subs","category":"Grill","item":"5-Star Subs","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Pizza","category":"Pasta & Pizza","item":"Pizza","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Calzones","category":"Pasta & Pizza","item":"Calzones","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Misc - Mozzarella Sticks","category":"Misc","item":"Mozzarella Sticks","healthy":false,"showCategory":false},{"descr":"Misc - Onion Petals","category":"Misc","item":"Onion Petals","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":25,"slug":"Becker-House-Dining","name":"Becker House Dining Room","nameshort":"Becker House Dining","about":"
\r\nThe Becker House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
Becker House Dining Room<\/strong><\/a>\r\n

\r\n
\"Becker<\/a>\r\n

","aboutshort":"Dining room located in Carl Becker House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"di2s9rofto7m8innt5e8vftl0o@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8882","contactEmail":null,"serviceUnitId":2,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.448336,"longitude":-76.489606,"location":"Carl Becker House","coordinates":{"latitude":42.448336,"longitude":-76.489606},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":10,"slug":"Big-Red-Barn","name":"Big Red Barn","nameshort":"Big Red Barn","about":"
\r\nThe Big Red Barn<\/strong> is a cozy caf\u00e9 with a delicious breakfast and lunch menu. It also serves as Cornell's on-campus social center for graduate and professional students. Relax at outdoor picnic tables, or sit inside by the fireplace in comfortable lounge furniture.\r\n

\r\nMore Info:
Big Red Barn<\/strong><\/a>\r\n

\r\n
\"Big<\/a>\r\n

","aboutshort":"Once a carriage house, the Big Red Barn is now a cozy caf\u00e9 with a delicious breakfast and lunch menu.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"u1kmovdep2qlmr86io8h4p3ee8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0428","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448526,"longitude":-76.48098,"location":"Big Red Barn","coordinates":{"latitude":42.448526,"longitude":-76.48098},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":11,"slug":"Bug-Stop-Bagels","name":"Bus Stop Bagels","nameshort":"Bus Stop Bagels","about":"
\r\nBus Stop Bagels<\/strong>, next to the Trillium food court in Kennedy Hall, gets fresh bagels delivered twice a day from Ithaca Bakery with all the usual toppings and a great selection of house sandwiches, plus Starbucks coffee drinks and snack foods.\r\n

\r\nMore Info:
Bus Stop Bagels<\/strong><\/a>\r\n

\r\n
\"Bus<\/a>\r\n

","aboutshort":"Bagels for breakfast, bagels for lunch, bagels to go!","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"rpp0nrlp282t9h18hhol5f0dkc@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447839,"longitude":-76.479456,"location":"Kennedy Hall","coordinates":{"latitude":42.447839,"longitude":-76.479456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640116800,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640203200,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640289600,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Coffee Bar - 96oz Coffee2Go","category":"Coffee Bar","item":"96oz Coffee2Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":12,"slug":"Cafe-Jennie","name":"Caf\u00e9 Jennie","nameshort":"Caf\u00e9 Jennie","about":"
\r\nLocated in The Cornell Store, Caf\u00e9 Jennie is named for Jennie McGraw, the daughter of John McGraw, a wealthy industrialist and a founding Cornell Trustee. Stop by for a Peet's Coffee, delicious Cheesecake Factory baked goods, and a mouth-watering array of sandwiches and wraps.\r\n

\r\nMore Info:
Caf\u00e9 Jennie<\/strong><\/a>\r\n

\r\n
\"Caf\u00e9<\/a>\r\n

","aboutshort":"A caf\u00e9 and sandwich\/pastry shop located in The Cornell Store.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"geron0aq1ooj7jugmcmdc2s2cc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8095","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446851,"longitude":-76.484376,"location":"The Cornell Store","coordinates":{"latitude":42.446851,"longitude":-76.484376},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640098800,"endTimestamp":1640120400,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640185200,"endTimestamp":1640206800,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640271600,"endTimestamp":1640286000,"start":"10:00am","end":"2:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Javiva","category":"Coffee Bar","item":"Javiva","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":2,"slug":"Carols-Cafe","name":"Carol's Caf\u00e9","nameshort":"Carol's Caf\u00e9","about":"
\r\nCome visit Carol's Caf\u00e9<\/strong> and enjoy a menu featuring specialty coffee from Starbucks, smoothies, hot daily soup selections, sushi, fresh fruit and snacks, Freshtake Grab-n-Go sandwiches and salads, delicious pastries and baked goods and more.\r\n

\r\nMore Info:
Carol's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Carol's<\/a>\r\n

","aboutshort":"Warm, inviting caf\u00e9 at Carol Tatkon Center open to the whole campus community.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"05r2nhfjbnknmsccgd6u8dij0g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2257","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453236,"longitude":-76.479404,"location":"Carol Tatkon Center, Balch Hall","coordinates":{"latitude":42.453236,"longitude":-76.479404},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":26,"slug":"Cook-House-Dining","name":"Cook House Dining Room","nameshort":"Cook House Dining","about":"
\r\nThe Cook House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. The eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Cook House Dining Room<\/strong><\/a>\r\n

\r\n
\"Cook<\/a>\r\n

","aboutshort":"Dining room located in Alice Cook House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"27hli58rto1hpf15m3sbe54sak@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-9508","contactEmail":null,"serviceUnitId":1,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.44889,"longitude":-76.488919,"location":"Alice Cook House","coordinates":{"latitude":42.44889,"longitude":-76.488919},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":14,"slug":"Cornell-Dairy-Bar","name":"Cornell Dairy Bar","nameshort":"Cornell Dairy Bar","about":"
\r\nIn the beautifully renovated Stocking Hall on the east end of Tower Road, the Cornell Dairy Bar is a great place for breakfast, coffee, or even sweet treat like Cornell ice cream, sundaes and floats. Regular hours vary seasonally, especially weekend hours, so please check the specific hours on this page for updates.\r\n

\r\nMore Info:
Cornell Dairy Bar<\/strong><\/a>\r\n

\r\n
\"Cornell<\/a>\r\n

","aboutshort":"In the renovated Stocking Hall, the Cornell Dairy features a variety of delicious Cornell Dairy ice cream, sundaes, and floats.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"prvu4v0nr4eu94mqu9q7busa6g@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7660","contactEmail":"dairybar@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447301,"longitude":-76.471398,"location":"Stocking Hall","coordinates":{"latitude":42.447301,"longitude":-76.471398},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"icecream-pink"},{"id":41,"slug":"Crossings-Cafe","name":"Crossings Caf\u00e9","nameshort":"Crossings Caf\u00e9","about":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","aboutshort":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"loadevpceh49fl3c8cuheb2dvk@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.4558,"longitude":-76.479386,"location":"Toni Morrison Hall","coordinates":{"latitude":42.4558,"longitude":-76.479386},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640124000,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640210400,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Mexican - Quesadillas","category":"Mexican","item":"Quesadillas","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Peet's Coffee","category":"Coffee Bar","item":"Peet's Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":32,"slug":"frannys","name":"Franny's","nameshort":"Franny's","about":"One of Cornell's newest eateries is Franny's, a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls. You'll find a unique mix of Pan-Asian sandwiches, rice bowls and ramen, bao buns and fries, as well as refreshing Asian-inspired drinks.","aboutshort":"Franny's is a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls, featuring a unique mix of Asian-inspired cuisine.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekdays","googleCalendarId":"cornell.edu_26ui0ai54lp0cdp2m3j27ci86c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0293","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.451053,"longitude":-76.483884,"location":"Next to Sibley Hall","coordinates":{"latitude":42.451053,"longitude":-76.483884},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[{"name":"Asian","nameshort":"Asian","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Asian - Rice Bowls","category":"Asian","item":"Rice Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Noodle Bowls","category":"Asian","item":"Noodle Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Banh Mi Sandwiches","category":"Asian","item":"Banh Mi Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":16,"slug":"Goldies-Cafe","name":"Goldie's Caf\u00e9","nameshort":"Goldie's Caf\u00e9","about":"
\r\nGoldie's is a great location on Central Campus for breakfast, lunch, or a mid-day snack. Signature sandwiches \u2013 some served on German-style pretzel rolls \u2013 have become customer favorites, and a wide array of Freshtake Grab-n-Go items, snacks, and desserts are always on the menu.\r\n

\r\nMore Info:
Goldie's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Goldie's<\/a>\r\n

","aboutshort":"Conveniently located in the Physical Sciences Building on Central Campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"kb9ce5jj2f6oli3c90tc7j6peo@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-6775","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450064,"longitude":-76.481503,"location":"Physical Sciences Building","coordinates":{"latitude":42.450064,"longitude":-76.481503},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":15,"slug":"Green-Dragon","name":"Green Dragon","nameshort":"Green Dragon","about":"
\r\nStop by the Green Dragon<\/strong> and enjoy a hot specialty Finger Lakes Coffee Roasters coffee, Freshtake Grab-n-Go sandwiches and salads, kosher items, and delicious desserts.\r\n

\r\nMore Info:
Green Dragon<\/strong><\/a>\r\n

\r\n
\"Green<\/a>\r\n

","aboutshort":"A hot spot on central campus, especially for the students, faculty, and staff of College of Architecture, Art and Planning.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"7sii70faon9ta2vpoehr69415s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3327","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450948,"longitude":-76.484456,"location":"Sibley Hall","coordinates":{"latitude":42.450948,"longitude":-76.484456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":24,"slug":"Hot-Dog-Cart","name":"Hot Dog Cart","nameshort":"Hot Dog Cart","about":"
\r\nCornell Dining's Hot Dog Cart<\/strong> is usually open outside Day Hall on summer weekdays when weather permits. On special occasions, the Hot Dog Cart may be elsewhere on campus. Keep an eye on Cornell Dining's
Facebook<\/a> and Twitter<\/a> for updates.\r\n

\r\nMore Info:
Hot Dog Cart<\/strong><\/a>\r\n

\r\n
\"Hot<\/a>\r\n

","aboutshort":"Enjoy lunch al fresco when weather permits, choosing an all-beef hot dog or vegetarian (tofu) hot dog.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"11:00am - 2:00pm weekdays, weather permitting","googleCalendarId":"cornell.edu_eaq3euadrebh0dmgqt618l7tgs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":34,"slug":"icecreamcart","name":"Ice Cream Bike","nameshort":"Ice Cream Bike","about":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","aboutshort":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekday middays for the summer, and Friday evenings for Cornell's concert series.","googleCalendarId":"cornell.edu_3kuppj4nsjes2b42jhpno5u97g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"icecream-pink"},{"id":27,"slug":"Jansens-Dining","name":"Jansen's Dining Room at Bethe House","nameshort":"Jansen's Dining","about":"
\r\nJansen's Dining Room at Bethe House<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Chef Jacob Kuehn puts together exciting new menus throughout the year. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Jansen's Dining Room at Bethe House<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Dining room located in Hans Bethe House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h0nfohf0d90ot1rmukjphj7ajc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1736","contactEmail":null,"serviceUnitId":5,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447116,"longitude":-76.48864,"location":"Hans Bethe House","coordinates":{"latitude":42.447116,"longitude":-76.48864},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":28,"slug":"Jansens-Market","name":"Jansen's Market","nameshort":"Jansen's Market","about":"
\r\nIn addition to an array of snacks, beverages, fresh and frozen take-away meals, household items, and pharmacy and beauty supplies, Jansen's Market<\/strong> also serve Starbucks coffee, bubble tea, smoothies, pastries, frozen yogurt, and Dreamfactory cheesecake.\r\n

\r\nMore Info:
Jansen's Market<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Full-service convenience store located on the first floor of Noyes Community Recreation Center on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"0dqnc6l2mt25okch8nimsnojhg@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-4997","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446325,"longitude":-76.487932,"location":"Noyes Community Recreation Center","coordinates":{"latitude":42.446325,"longitude":-76.487932},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Convenience Store","descrshort":"convenience store"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Bubble Tea","category":"Coffee Bar","item":"Bubble Tea","healthy":false,"showCategory":false},{"descr":"Misc - Peanut Butter Sandwich Bar","category":"Misc","item":"Peanut Butter Sandwich Bar","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":29,"slug":"Keeton-House-Dining","name":"Keeton House Dining Room","nameshort":"Keeton House Dining","about":"
\r\nThe Keeton House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Sample Chef Nery's unique menu offerings in the entr\u00e9e station, Asian station, grill, deli, and salad bar. This eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Keeton House Dining Room<\/strong><\/a>\r\n

\r\n
\"Keeton<\/a>\r\n

","aboutshort":"Dining room located in William Keeton House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ekd72jfc2qai617oloa2b0ibp0@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3033","contactEmail":null,"serviceUnitId":3,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446942,"longitude":-76.489992,"location":"William Keeton House","coordinates":{"latitude":42.446942,"longitude":-76.489992},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":42,"slug":"Mann-Cafe","name":"Mann Caf\u00e9","nameshort":"Mann Caf\u00e9","about":"Take a study break at Mann Caf\u00e9!","aboutshort":"Take a study break at Mann Caf\u00e9!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ppbukfcjo05629gk0t4fu26aro@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448799,"longitude":-76.47851,"location":"Mann Library on the Ag Quad","coordinates":{"latitude":42.448799,"longitude":-76.47851},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Mexican - Burritos","category":"Mexican","item":"Burritos","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":18,"slug":"Marthas-Cafe","name":"Martha's Caf\u00e9","nameshort":"Martha's Cafe","about":"
\r\nMartha's Caf\u00e9<\/strong>, in Martha Van Rensselaer Hall, offers made-to-order Mediterranean-inspired grain and salad bowls and wraps, composed bowls, Copper Horse Coffee, and breakfast!\r\n

\r\nMore Info:
Martha's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Martha's<\/a>\r\n

","aboutshort":"Fresh food with a Mediterranean flair.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sperf092mrbt796rr36toeqrus@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-8080","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450115,"longitude":-76.479237,"location":"Martha Van Rensselaer Hall","coordinates":{"latitude":42.450115,"longitude":-76.479237},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Milk","category":"Cornell Dairy","item":"Milk","healthy":false,"showCategory":true},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false},{"descr":"Lunch - Hot and Cold Mediterranean Bowls","category":"Lunch","item":"Hot and Cold Mediterranean Bowls","healthy":true,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":19,"slug":"Mattins-Cafe","name":"Mattin's Caf\u00e9","nameshort":"Mattin's Caf\u00e9","about":"
\r\nYou\u2019ll find Mattin's Caf\u00e9<\/strong> in the atrium of Duffield Hall, adjacent to Phillips Hall on the Engineering Quad. Enjoy made-to-order deli sandwiches, boneless wings, hot Starbucks coffee, Freshtake Grab-n-Go items, soups, kosher items, mouth-watering pastries and more.\r\n

\r\nMore Info:
Mattin's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Mattin's<\/a>\r\n

","aboutshort":"Popular with engineers and a go to spot in the stunning atrium of Duffield Hall.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"1qman2n728pqjuq5ntaoofc7v0@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-4581","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.444162,"longitude":-76.482287,"location":"Duffield Hall","coordinates":{"latitude":42.444162,"longitude":-76.482287},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640113200,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640199600,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":33,"slug":"mccormicks","name":"McCormick's at Moakley House","nameshort":"McCormick's","about":"McCormick's at Moakley House offers a casual environment close to campus, open daily through the season. Treat a campus visitor to an impeccably served luncheon, stop for refreshments after a round of golf, meet coworkers at the end of the day for a burger and a beer, or bring the family for a relaxing weekend brunch.\r\n\r\nNamed for Jack McCormick, a varsity golfer from the Cornell Class of 1957 whose will included a bequest to modernize Moakley House, this eatery alongside the award-winning Robert Trent Jones Golf Course at Cornell University isn't just for golfers. It's open to the public and has plenty of parking, and we welcome everyone for a drink, a snack, or a meal.\r\n\r\nMcCormick's is closed for the 2021 season as of September 24th.\r\n\r\n

\r\nMore Info:
McCormick's at Moakley House<\/strong><\/a>\r\n

","aboutshort":"Named for Jack McCormick, a varsity golfer from the Cornell Class of 1957, McCormick's is open to the public and has plenty of parking.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"cornell.edu_gqlmrg7n0qk8qihl75ru2u1p80@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-6536","contactEmail":"mccormicks@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.458324,"longitude":-76.469539,"location":"Robert Trent Jones Golf Course","coordinates":{"latitude":42.458324,"longitude":-76.469539},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false}],"announcements":[{"id":199,"title":"McCormick's is closed for the 2021 season after Friday, September 24th.","announceType":"EATERY","startTimestamp":1632283200,"stopTimestamp":1640408340}],"icon":"coffee-brown"},{"id":3,"slug":"North-Star","name":"North Star Dining Room","nameshort":"North Star","about":"
\r\nThe North Star Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Enjoy an open kitchen concept and numerous unique food stations serving a huge variety of ethnic and regional American cuisine. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
North Star Dining Room<\/strong><\/a>\r\n

\r\n
\"North<\/a>\r\n

","aboutshort":"Dining room located in Appel Commons on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ecbhqf3ibeei09dds91viod5g8@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2992","contactEmail":null,"serviceUnitId":7,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453527,"longitude":-76.475944,"location":"Appel Commons, Third floor","coordinates":{"latitude":42.453527,"longitude":-76.475944},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[{"id":216,"title":"North Star Dining Room is closed for renovation through the Spring 2022 semester!","announceType":"EATERY","startTimestamp":1639976400,"stopTimestamp":1654055940}],"icon":"restaurant-red"},{"id":20,"slug":"Okenshields","name":"Okenshields","nameshort":"Okenshields","about":"
\r\nOkenshields<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. With hundreds of menu options to choose from \u2013 from an Asian station to a healthy foods bar featuring whole grain salads and cut fruit \u2013 you're sure to find something that meets your fancy.\r\n

\r\nMore Info:
Okenshields<\/strong><\/a>\r\n

\r\n
\"Okenshields<\/a>\r\n

","aboutshort":"Dining room located in Willard Straight Hall on Central Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"3hku0mr66kapq1lh8fakug9kko@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6636","contactEmail":null,"serviceUnitId":10,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446491,"longitude":-76.485678,"location":"Willard Straight Hall","coordinates":{"latitude":42.446491,"longitude":-76.485678},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":4,"slug":"Risley-Dining","name":"Risley Dining Room","nameshort":"Risley Dining Room","about":"
\r\nRisley Dining Room<\/strong> is a 100% gluten-free, tree-nut-free, and peanut-free
dining room<\/a> for house residents, and for the entire Cornell community. There are always vegan and vegetarian choices. Modeled after the Christchurch Refectory at Oxford University, the dining room maintains the same Gothic charm as when it first opened as the all-Ivy \"Risley Great Hall\" in 1913. \r\n

\r\nMore Info:
Risley Dining Room<\/strong><\/a>\r\n

\r\n
\"Risley<\/a>\r\n

","aboutshort":"Dining room located in Risley Residential College on North Campus.","nutrition":"Risley Dining Room is certified 100% gluten-free, tree-nut-free, and peanut-free. Thank you for not bringing outside food into Risley!<\/strong>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"hq98btd396f3077p88d30c84fs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-4229","contactEmail":null,"serviceUnitId":8,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453117,"longitude":-76.481946,"location":"Risley Residential College","coordinates":{"latitude":42.453117,"longitude":-76.481946},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":5,"slug":"RPCC-Marketplace","name":"Robert Purcell Marketplace Eatery","nameshort":"RPCC Marketplace","about":"
\r\nThe Robert Purcell Marketplace Eatery<\/strong> is an award-winning
dining room<\/a> with a huge variety of menu options. Chef Kevin Moore, who has been with Cornell Dining for over 25 years, most recently at 104West!<\/a>, plans creative menus with roots in a variety of international traditions. Note: Our Late Dinner hours feature a limited menu.\r\n

\r\nMore Info:
Robert Purcell Marketplace Eatery<\/strong><\/a>\r\n

\r\n
\"Robert<\/a>\r\n

","aboutshort":"Dining room located in Robert Purcell Community Center on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"32uglqeiqfo9edhpp4tka8oqsc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1138","contactEmail":null,"serviceUnitId":6,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455973,"longitude":-76.477354,"location":"RPCC, Third floor","coordinates":{"latitude":42.455973,"longitude":-76.477354},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640088000,"endTimestamp":1640100600,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":2,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Pork Breakfast Sausage","healthy":false,"sortIdx":3},{"item":"Home Fries","healthy":true,"sortIdx":4},{"item":"Pancakes with Syrup","healthy":false,"sortIdx":5},{"item":"French Toast Sticks","healthy":false,"sortIdx":6},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":7}]},{"category":"Grill Station","sortIdx":3,"items":[{"item":"Scrambled Tofu","healthy":true,"sortIdx":1},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":2}]},{"category":"Specialty Station","sortIdx":4,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":2},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":3},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":4},{"item":"Waffle Bar","healthy":false,"sortIdx":5},{"item":"Assorted Cereal","healthy":false,"sortIdx":6},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":7}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640100600,"endTimestamp":1640115000,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":5,"items":[{"item":"Beef Vegetable Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":6,"items":[{"item":"Salad Bar","healthy":true,"sortIdx":1},{"item":"Grains For Brains","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":7,"items":[{"item":"Pasta Primavera","healthy":false,"sortIdx":1},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":8,"items":[{"item":"Potato Tots","healthy":false,"sortIdx":1},{"item":"Calabacitas","healthy":true,"sortIdx":2},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":9,"items":[{"item":"Hacienda Cheese Quesadilla","healthy":false,"sortIdx":1},{"item":"Grilled Cheese Sandwich","healthy":false,"sortIdx":2}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640124000,"endTimestamp":1640133000,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":10,"items":[{"item":"Cornell Chicken Noodle Soup","healthy":false,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":11,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2},{"item":"Grains For Brains","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":12,"items":[{"item":"Salmon","healthy":false,"sortIdx":1},{"item":"Spanish Style Brown Rice & Beans","healthy":true,"sortIdx":2},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Sides","sortIdx":13,"items":[{"item":"Steamed Vegetable Melange","healthy":true,"sortIdx":1},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":2},{"item":"Sauteed Zucchini","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4},{"item":"Fried Potato Puffs","healthy":false,"sortIdx":5}]},{"category":"Grill Station","sortIdx":14,"items":[{"item":"BBQ Chicken Pizza","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640174400,"endTimestamp":1640187000,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":15,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Turkey Breakfast Sausage","healthy":true,"sortIdx":3},{"item":"Bacon","healthy":false,"sortIdx":4},{"item":"Steamed Brown Rice","healthy":true,"sortIdx":5},{"item":"Home Fries","healthy":true,"sortIdx":6}]},{"category":"Grill Station","sortIdx":16,"items":[{"item":"Bacon & Cheese Omelet","healthy":false,"sortIdx":1},{"item":"Steamed Fresh Vegetables","healthy":true,"sortIdx":2},{"item":"Western Scrambled Tofu","healthy":true,"sortIdx":3},{"item":"Build Your Own Omelette","healthy":true,"sortIdx":4},{"item":"Cheese Omelet","healthy":false,"sortIdx":5}]},{"category":"Specialty Station","sortIdx":17,"items":[{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":1},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":2},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":3},{"item":"Waffle Bar","healthy":false,"sortIdx":4},{"item":"Assorted Cereal","healthy":false,"sortIdx":5},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":6}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640187000,"endTimestamp":1640201400,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":18,"items":[{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":19,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Salad Bar","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":20,"items":[{"item":"Pasta with Sun-dried Tomato Basil & Feta","healthy":true,"sortIdx":1},{"item":"Kale Pesto Chicken","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":21,"items":[{"item":"Chef's Choice Seasonal Vegetable","healthy":true,"sortIdx":1},{"item":"Seared Kale","healthy":true,"sortIdx":2},{"item":"Rosemary Roasted Red Skin Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":22,"items":[{"item":"Grilled Chicken Breast","healthy":true,"sortIdx":1},{"item":"Grilled Hot Dogs","healthy":false,"sortIdx":2},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4}]},{"category":"Pizza Station","sortIdx":23,"items":[{"item":"Supreme Vegetable Pizza","healthy":false,"sortIdx":1},{"item":"White Garlic Hawaiian Pizza","healthy":false,"sortIdx":2}]},{"category":"Wok\/Asian Station","sortIdx":24,"items":[{"item":"Moo Goo Gai Pan","healthy":false,"sortIdx":1},{"item":"Vegetable Fried Rice","healthy":false,"sortIdx":2},{"item":"Long Grain Brown Rice","healthy":true,"sortIdx":3},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":4}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640210400,"endTimestamp":1640219400,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":25,"items":[{"item":"Cornell Cream of Mushroom Soup","healthy":false,"sortIdx":1},{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":2}]},{"category":"Salad Bar Station","sortIdx":26,"items":[{"item":"Grains For Brains","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Entrees","sortIdx":27,"items":[{"item":"Roasted Eggplant & Zucchini Casserole","healthy":false,"sortIdx":1},{"item":"Chef's Choice Vegan Entree","healthy":false,"sortIdx":2},{"item":"Chef's Choice Meat Entree","healthy":false,"sortIdx":3},{"item":"Roasted Pork Loin with Mango Mojo","healthy":true,"sortIdx":4},{"item":"Gourmet Pretzel Bar","healthy":false,"sortIdx":5}]},{"category":"Hot Traditional Station - Sides","sortIdx":28,"items":[{"item":"Steamed Winter Vegetables","healthy":true,"sortIdx":1},{"item":"Sauteed Super Greens","healthy":true,"sortIdx":2},{"item":"Roasted Garlic & Herb Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":29,"items":[{"item":"Veggie Burger","healthy":true,"sortIdx":1},{"item":"Char-Grilled Hamburgers","healthy":false,"sortIdx":2},{"item":"Build Your Own Nachos","healthy":false,"sortIdx":3}]},{"category":"Pasta and Pizza Station","sortIdx":30,"items":[{"item":"Pasta By Design","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2},{"item":"Broccoli Alfredo Pizza","healthy":false,"sortIdx":3},{"item":"Buffalo Chicken Pizza","healthy":false,"sortIdx":4}]},{"category":"Wok\/Asian Station","sortIdx":31,"items":[{"item":"Pancake Bar with Fruit Toppings & Syrups","healthy":false,"sortIdx":1},{"item":"Omelet Bar","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640260800,"endTimestamp":1640273400,"start":"7:00am","end":"10:30am","menu":[],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640273400,"endTimestamp":1640287800,"start":"10:30am","end":"2:30pm","menu":[],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640296800,"endTimestamp":1640305800,"start":"5:00pm","end":"7:30pm","menu":[],"calSummary":"Dinner"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":30,"slug":"Rose-House-Dining","name":"Rose House Dining Room","nameshort":"Rose House Dining","about":"
\r\nThe Rose House Dining Room<\/strong> is a
dning room<\/a> for house residents, and for the entire Cornell community. This eatery features traditional hot entrees, an Asian station, a grill, a deli, and a salad bar. Continental breakfast on Saturday and Sunday. Sample Chef Matt Seeber's daily menu options \u2013 you won't be disappointed!\r\n

\r\nMore Info:
Rose House Dining Room<\/strong><\/a>\r\n

\r\n
\"Rose<\/a>\r\n

","aboutshort":"Dining room located in Flora Rose House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"mo4mqfpe88ucqaer728ovfei18@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-0337","contactEmail":null,"serviceUnitId":4,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447813,"longitude":-76.488791,"location":"Flora Rose House","coordinates":{"latitude":42.447813,"longitude":-76.488791},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":21,"slug":"Rustys","name":"Rusty's","nameshort":"Rusty's","about":"
\r\nConveniently located in the lobby of Uris Hall, Rusty's<\/strong> offers Starbucks specialty coffees, Straight from the Oven baked goods, Freshtake Grab-n-Go sandwiches, salads and more. At any time of day, you're sure to find something to whet your appetite!\r\n

\r\nMore Info:
Rusty's<\/strong><\/a>\r\n

\r\n
\"Rusty's<\/a>\r\n

","aboutshort":"A great place to grab a quick coffee or a bite to eat on your way to class or work.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sqp9nd9rt727fm7v2sgmfelkps@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6656","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447399,"longitude":-76.482302,"location":"Uris Hall","coordinates":{"latitude":42.447399,"longitude":-76.482302},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":13,"slug":"StraightMarket","name":"Straight from the Market","nameshort":"Straight from the Market","about":"With a farm-fresh marketplace flair, Straight from the Market offers a wide variety of fresh and marinated vegetables, hummus and tapenade bar, salad fixings, and Cornell Dairy ice cream on the main floor of Willard Straight Hall adjacent to the Straight Terrace. Healthy, flavorful, and ready-to-go meat, vegan, and vegetarian choices daily.","aboutshort":"A farm-fresh marketplace on the main floor of the Straight.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ju94n6trv0ccoqcnd5u7otle50@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3963","contactEmail":null,"serviceUnitId":11,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446372,"longitude":-76.485786,"location":"Willard Straight Hall","coordinates":{"latitude":42.446372,"longitude":-76.485786},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":23,"slug":"Trillium","name":"Trillium","nameshort":"Trillium","about":"
\r\nAt Trillium<\/strong> choose from a wide variety of food stations serving Mexican food, Asian dishes, sumptuous burgers, pasta bar, sandwiches, salads, soups, and made-to-order omelets, among many other delectable menu options.\r\n

\r\nMore Info:
Trillium<\/strong><\/a>\r\n

\r\n
\"Trillium<\/a>\r\n

","aboutshort":"Located in Kennedy Hall in the heart of Central Campus, Trillium is one of our most popular food courts.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"i8v43jd76mugc62voucp4dqn9s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447883,"longitude":-76.479127,"location":"Kennedy Hall","coordinates":{"latitude":42.447883,"longitude":-76.479127},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Food Court","descrshort":"food court"}],"diningCuisines":[{"name":"Mexican","nameshort":"Mexican","descr":""},{"name":"Asian","nameshort":"Asian","descr":""},{"name":"Pizza","nameshort":"Pizza","descr":""},{"name":"Italian","nameshort":"Italian","descr":null}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"fastfood-blue"}]},"message":null,"meta":{"copyright":"Cornell University, Cornell Dining","responseDttm":"2021-12-22T11:15:18-0500"}} \ No newline at end of file diff --git a/static_sources/external_eateries.json b/static_sources/external_eateries.json deleted file mode 100644 index c927f6a..0000000 --- a/static_sources/external_eateries.json +++ /dev/null @@ -1,828 +0,0 @@ -{ - "eateries": [ - { - "id": 33, - "slug": "Terrace", - "external": true, - "name": "Terrace Restaurant", - "nameshort": "Terrace", - "about": "", - "contactPhone": "1-800-541-2501", - "latitude": 42.446267, - "longitude": -76.482314, - "location": "Statler", - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - } - ], - "datesClosed": [ - "9/6/21", - "11/25/21", - "11/26/21", - "11/27/21", - "11/28/21", - "12/20/21-1/20/22" - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - }, - { - "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", - "descrshort": "Meal Plan - Debit" - } - ], - "diningItems": [ - { - "item": "Salads", - "healthy": true, - "category": "General" - }, - { - "item": "Burrito Bowl", - "healthy": false, - "category": "General" - }, - { - "item": "Burrito", - "healthy": true, - "category": "General" - }, - { - "item": "Chicken Tenders", - "healthy": false, - "category": "General" - }, - { - "item": "Fries", - "healthy": false, - "category": "General" - }, - { - "item": "Pho", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 34, - "slug": "Macs", - "external": true, - "name": "Mac's Café", - "nameshort": "Mac's", - "about": "", - "cornellDining": false, - "contactPhone": "1-800-541-2501", - "latitude": 42.445921, - "longitude": -76.481984, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Statler Hotel", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - } - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - }, - { - "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", - "descrshort": "Meal Plan - Debit" - } - ], - "datesClosed": [ - "8/18/21", - "8/19/21", - "8/20/21", - "8/21/21", - "8/22/21", - "8/23/21", - "9/6/21", - "10/9/21", - "10/10/21", - "10/11/21", - "10/12/21", - "11/24/21", - "11/25/21", - "11/26/21", - "11/27/21", - "11/28/21", - "12/16/21", - "12/17/21", - "12/20/21-1/20/22" - ], - "diningItems": [ - { - "item": "Pizza", - "healthy": false, - "category": "General" - }, - { - "item": "Pasta", - "healthy": false, - "category": "General" - }, - { - "item": "Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Sushi to Go", - "healthy": false, - "category": "General" - }, - { - "item": "Soft Drinks", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 35, - "slug": "Zeus", - "external": true, - "name": "Temple of Zeus", - "nameshort": "Temple of Zeus", - "about": "", - "cornellDining": false, - "contactPhone": "", - "latitude": 42.449091, - "longitude": -76.483414, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Goldwin Smith Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - } - ], - "datesClosed": ["09/06/21"], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Sandwiches", - "healthy": true, - "category": "General" - }, - { - "item": "Soups", - "healthy": true, - "category": "General" - }, - { - "item": "Baked Goods", - "healthy": true, - "category": "General" - }, - { - "item": "Candy", - "healthy": false, - "category": "General" - }, - { - "item": "Soft Drinks", - "healthy": true, - "category": "General" - } - ] - }, - { - "id": 36, - "slug": "Gimme-Coffee", - "external": true, - "name": "Gimme Coffee", - "nameshort": "Gimme Coffee", - "about": "", - "cornellDining": false, - "contactPhone": "1-607-227-5391", - "latitude": 42.444958, - "longitude": -76.481169, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Gates Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Coffee", - "healthy": true, - "category": "General" - }, - { - "item": "Baked Goods", - "healthy": true, - "category": "General" - } - ] - }, - { - "id": 37, - "slug": "Louies-Lunch", - "external": true, - "name": "Louie's Lunch", - "nameshort": "Louie's", - "about": "", - "cornellDining": false, - "contactPhone": "1-607-257-4649", - "latitude": 42.45336, - "longitude": -76.481225, - "campusArea": { - "descr": "North Campus", - "descrshort": "North" - }, - "location": "Across from Risley", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Saturday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Sunday", - "events": [ - { - "descr": "General", - "start": "18:00", - "end": "00:00", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "French Fries", - "healthy": false, - "category": "General" - }, - { - "item": "Garlic Bread", - "healthy": false, - "category": "General" - }, - { - "item": "Cold Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Subs", - "healthy": false, - "category": "General" - }, - { - "item": "Pizza Subs", - "healthy": false, - "category": "General" - }, - { - "item": "Burgers", - "healthy": false, - "category": "General" - }, - { - "item": "Egg Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Dogs", - "healthy": false, - "category": "General" - }, - { - "item": "Wraps", - "healthy": false, - "category": "General" - }, - { - "item": "Salads", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 38, - "slug": "Anabels-Grocery", - "external": true, - "name": "Anabel's Grocery", - "nameshort": "Anabel's", - "about": "", - "cornellDining": false, - "contactPhone": "", - "latitude": 42.445061, - "longitude": -76.485826, - "campusArea": { - "descr": "South Campus", - "descrshort": "South" - }, - "location": "Anabel Taylor Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "19:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "19:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "19:00", - "menu": [] - } - ] - }, - { - "weekday": "Saturday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "15:00", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Cornell Card", - "descrshort": "Cornell Card" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Whole Grains", - "healthy": true, - "category": "General" - }, - { - "item": "Spices", - "healthy": false, - "category": "General" - }, - { - "item": "Legumes", - "healthy": true, - "category": "General" - }, - { - "item": "Fresh and Frozen Produce", - "healthy": true, - "category": "General" - }, - { - "item": "Nuts", - "healthy": true, - "category": "General" - }, - { - "item": "Dried Fruits", - "healthy": true, - "category": "General" - }, - { - "item": "Tofu", - "healthy": true, - "category": "General" - }, - { - "item": "Eggs", - "healthy": false, - "category": "General" - }, - { - "item": "Milks", - "healthy": true, - "category": "General" - }, - { - "item": "Kombucha on Tap", - "healthy": true, - "category": "General" - }, - { - "item": "Bottled Drinks", - "healthy": false, - "category": "General" - }, - { - "item": "Bulk Items", - "healthy": false, - "category": "General" - } - ] - } - ] -} From 905e65980911240121df705d20092f3536697fbd Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 15 Nov 2023 18:29:51 -0500 Subject: [PATCH 179/305] Add filter to not return cafes for upcoming menus --- src/eatery/serializers.py | 8 ++++++-- src/eatery/views.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 1e42c1f..0e70bb4 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -25,6 +25,10 @@ def create(self, validated_data): eatery, _ = Eatery.objects.get_or_create(**validated_data) return eatery + class Meta: + model = Eatery + fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + class EateryReadSerializer(serializers.ModelSerializer): id = serializers.IntegerField() name = serializers.CharField() @@ -57,7 +61,7 @@ class Meta: class EaterySerializerByDay(serializers.ModelSerializer): menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") - events = serializers.SerializerMethodField(many=True) + events = serializers.SerializerMethodField() def get_events(self, obj): day = self.context.get("day") @@ -66,7 +70,7 @@ def get_events(self, obj): end_today = today + timedelta(days=1) today_unix = mktime(today.timetuple()) + 18000 end_today_unix = mktime(end_today.timetuple()) + 18000 - events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, end__lte=end_today_unix) + events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=end_today_unix) serializer = EventReadSerializer(instance=events, many=True) return serializer.data diff --git a/src/eatery/views.py b/src/eatery/views.py index 511cef1..ff29c6e 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -98,5 +98,5 @@ class GetEateriesByDay(APIView): Get all eatery information by day """ def get(self, request, day): - eateries = EaterySerializerByDay(Eatery.objects.all(), many=True, context={"day": day}) + eateries = EaterySerializerByDay(Eatery.objects.exclude(events__event_description="Open"), many=True, context={"day": day}) return Response(eateries.data) \ No newline at end of file From 604738ad726092f234a69d3838aa6fa1a9146116 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Thu, 16 Nov 2023 21:03:03 -0500 Subject: [PATCH 180/305] Switched back to EaterySerializer --- src/eatery/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/eatery/views.py b/src/eatery/views.py index ff29c6e..347782f 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -23,12 +23,12 @@ class EateryViewSet(viewsets.ModelViewSet): def retrieve(self, request, *args, **kwargs): instance = self.get_object() - serializer = EateryReadSerializer(instance) + serializer = EaterySerializer(instance) return Response(serializer.data) def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) - serializer = EateryReadSerializer(queryset, many=True) + serializer = EaterySerializer(queryset, many=True) return Response(serializer.data) From 0a69c076cd3ee114e9ec9062427f43bd733a11bb Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Mon, 27 Nov 2023 14:28:50 -0800 Subject: [PATCH 181/305] make prod autodeploy (#75) --- .github/workflows/deploy-dev.yml | 4 ++-- .github/workflows/deploy-prod.yml | 38 +++++++++++++++++++++++++++++++ docker-compose.server.yml | 2 +- 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/deploy-prod.yml diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index abec60a..cfa768e 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -1,4 +1,4 @@ -name: Docker Build & Push and Deploy to eatery-blue-backend +name: Docker Build & Push and Deploy to dev for eatery-blue-backend on: push: @@ -27,7 +27,7 @@ jobs: context: ./ file: ./Dockerfile push: true - tags: cornellappdev/eatery-blue-dev:${{ steps.vars.outputs.sha_short }} + tags: cornellappdev/eatery-blue:${{ steps.vars.outputs.sha_short }} - name: Remote SSH and Deploy uses: appleboy/ssh-action@master env: diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml new file mode 100644 index 0000000..1021be4 --- /dev/null +++ b/.github/workflows/deploy-prod.yml @@ -0,0 +1,38 @@ +name: Docker Build & Push and Deploy to prod for eatery-blue-backend + +on: + push: + branches: [release] + workflow_dispatch: + +jobs: + path-context: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Get SHA + id: vars + run: echo "::set-output name=sha_short::$(git rev-parse --short release)" + - name: Remote SSH and Deploy + uses: appleboy/ssh-action@master + env: + IMAGE_TAG: ${{ steps.vars.outputs.sha_short }} + with: + host: ${{ secrets.PROD_SERVER_HOST }} + username: ${{ secrets.SERVER_USERNAME }} + key: ${{ secrets.PROD_SERVER_KEY }} + script: | + export IMAGE_TAG=${{ steps.vars.outputs.sha_short }} + cd docker-compose + docker stack rm thestack + sleep 20s + docker stack deploy -c docker-compose.yml thestack + docker system prune -a diff --git a/docker-compose.server.yml b/docker-compose.server.yml index 93af826..f5c6eed 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -2,7 +2,7 @@ version: "3" services: app: - image: cornellappdev/eatery-blue-dev:${IMAGE_TAG} + image: cornellappdev/eatery-blue:${IMAGE_TAG} env_file: .env stdin_open: true # docker run -i tty: true # docker run -t From 2e4f0466a1a5879f997d992f16c7a2571cf2e75e Mon Sep 17 00:00:00 2001 From: Thomas Vignos <113550626+tjvignos@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:29:27 -0800 Subject: [PATCH 182/305] Switched back to EaterySerializer (#76) Co-authored-by: Thomas Vignos --- src/eatery/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/eatery/views.py b/src/eatery/views.py index ff29c6e..347782f 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -23,12 +23,12 @@ class EateryViewSet(viewsets.ModelViewSet): def retrieve(self, request, *args, **kwargs): instance = self.get_object() - serializer = EateryReadSerializer(instance) + serializer = EaterySerializer(instance) return Response(serializer.data) def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) - serializer = EateryReadSerializer(queryset, many=True) + serializer = EaterySerializer(queryset, many=True) return Response(serializer.data) From e808987fa1611088572bef5464d36db62f4c2091 Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Mon, 27 Nov 2023 14:53:57 -0800 Subject: [PATCH 183/305] fix time zone for external eateries (#77) --- src/event/controllers/populate_event.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py index 365d993..4409493 100644 --- a/src/event/controllers/populate_event.py +++ b/src/event/controllers/populate_event.py @@ -2,6 +2,7 @@ from event.serializers import EventSerializer from eatery.util.constants import dining_id_to_internal_id import json +import pytz class PopulateEventController(): def __init__(self): @@ -48,12 +49,14 @@ def generate_external_events(self, json_eatery): while date.strftime("%A").lower() != json_date["weekday"].lower(): date += timedelta(days=1) start_string = json_event['start'] - start_timestamp = datetime(date.year, date.month, date.day, int(start_string[:2]), int(start_string[3:])).timestamp() + timezone = pytz.timezone('US/Eastern') + start_time = datetime(date.year, date.month, date.day, int(start_string[:2]), int(start_string[3:])) + start_timestamp = timezone.localize(start_time).timestamp() end_string = json_event['end'] if int(end_string[:2]) < int(start_string[:2]): date += timedelta(days=1) - end_timestamp = datetime(date.year, date.month, date.day, int(end_string[:2]), int(end_string[3:])).timestamp() - + end_time = datetime(date.year, date.month, date.day, int(end_string[:2]), int(end_string[3:])) + end_timestamp = timezone.localize(end_time).timestamp() eatery_id = json_eatery["id"] data = { 'eatery': eatery_id, From 8d0e3bb790d684f2a81a338fb17d0f6db2701290 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Sun, 28 Jan 2024 21:31:37 -0500 Subject: [PATCH 184/305] Add back event ids --- src/event/serializers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event/serializers.py b/src/event/serializers.py index 40c0798..12dbd09 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -22,6 +22,7 @@ class Meta: fields = ["id", "eatery", "event_description", "start", "end", "menu"] class EventReadSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False, read_only=True) event_description = serializers.CharField(allow_null=True, allow_blank=True, default=None) start = serializers.IntegerField() end = serializers.IntegerField() @@ -29,7 +30,7 @@ class EventReadSerializer(serializers.ModelSerializer): class Meta: model = Event - fields = ["event_description", "start", "end", "menu"] + fields = ["id", "event_description", "start", "end", "menu"] class EventSerializerSimple(serializers.ModelSerializer): class Meta: From 23cf28dac5b5b189b21f2a7c3749138e1a0e268a Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 6 Feb 2024 22:43:42 -0500 Subject: [PATCH 185/305] Change where cronjob outputs --- src/cron_log.txt | 1 + update_db.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 src/cron_log.txt diff --git a/src/cron_log.txt b/src/cron_log.txt new file mode 100644 index 0000000..0e80df3 --- /dev/null +++ b/src/cron_log.txt @@ -0,0 +1 @@ +cron outputs here \ No newline at end of file diff --git a/update_db.txt b/update_db.txt index 4b28995..3e68ead 100644 --- a/update_db.txt +++ b/update_db.txt @@ -1 +1 @@ -0 8-21 * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /proc/1/fd/1 2>/proc/1/fd/2 +0 8-21 * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /usr/app/cron_log.txt 2>&1; From c4f5119c1f6c117140d85b7a34a90f6aadb924db Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 6 Feb 2024 22:45:09 -0500 Subject: [PATCH 186/305] Temporary change to diagnose cron --- update_db.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update_db.txt b/update_db.txt index 3e68ead..cf5923a 100644 --- a/update_db.txt +++ b/update_db.txt @@ -1 +1 @@ -0 8-21 * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /usr/app/cron_log.txt 2>&1; +*/15 * * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /usr/app/cron_log.txt 2>&1; From ec86cacfefeb2cd1aec79d4682897c9a2496809e Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 6 Feb 2024 23:05:16 -0500 Subject: [PATCH 187/305] Revert cron timing change --- update_db.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update_db.txt b/update_db.txt index cf5923a..3e68ead 100644 --- a/update_db.txt +++ b/update_db.txt @@ -1 +1 @@ -*/15 * * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /usr/app/cron_log.txt 2>&1; +0 8-21 * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /usr/app/cron_log.txt 2>&1; From 7cd512d4389c363906cec2bc19be2e21f2d46c1c Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 6 Feb 2024 23:40:52 -0500 Subject: [PATCH 188/305] Add security features --- src/eatery_blue_backend/settings.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 23fb2ab..a0e5b0f 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -21,10 +21,10 @@ # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-tup==8h@6!ewid!sfi*)jomsejj4j@=w=u*2ri9g0*0$3)1dkq" +SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = False ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(",") @@ -135,3 +135,9 @@ # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +SESSION_COOKIE_SECURE = True + +CSRF_COOKIE_SECURE = True + +SECURE_BROWSER_XSS_FILTER = True From d040b2b8a079e55f24e47d0b84bd4af55d525dbd Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Tue, 6 Feb 2024 23:50:02 -0500 Subject: [PATCH 189/305] Allow debug locally --- src/eatery_blue_backend/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index a0e5b0f..e25556b 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -24,7 +24,8 @@ SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = False +# IS_PROD can be None +DEBUG = False if os.environ.get("IS_PROD") else True ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(",") From 4aeb87532a6548266f3d9c631c47455e13c6edb8 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 7 Feb 2024 12:13:03 -0500 Subject: [PATCH 190/305] Upgrade populate model logging --- .../management/commands/populate_models.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index afd0248..4191bd0 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -14,7 +14,7 @@ def handle(self, *args, **kwargs): self.stdout.write(f"Populating models at {datetime.now()} UTC") start = int(datetime.now().timestamp()) self.process() - self.stdout.write(f"Populated models ({int(datetime.now().timestamp()) - start}s)") + self.stdout.write(f"Finished populating models at {datetime.now()} UTC ({int(datetime.now().timestamp()) - start}s)") def get_json(self): try: @@ -25,6 +25,13 @@ def get_json(self): response = response.json() json_eateries = response["data"]["eateries"] return json_eateries + + def logger_wrapper(self, command_obj, log_title, args): + pre = int(datetime.now().timestamp()) + print(f"{datetime.now()} UTC: {log_title}") + output = command_obj.process(*args) + print(f"Done ({int(datetime.now().timestamp()) - pre}s) ") + return output def process(self): """ @@ -51,19 +58,15 @@ def process(self): """ json_eateries = self.get_json() - + Event.truncate() - print("Populating eateries") - PopulateEateryController().process(json_eateries) + self.logger_wrapper(PopulateEateryController(), "Populating eateries", [json_eateries]) - print("Populating events") - events_dict = PopulateEventController().process(json_eateries) + events_dict = self.logger_wrapper(PopulateEventController(), "Populating events", [json_eateries]) - print("Populating categories") - categories_dict = PopulateCategoryController().process(events_dict, json_eateries) + categories_dict = self.logger_wrapper(PopulateCategoryController(), "Populating categories", [events_dict, json_eateries]) - print("Populating items") - PopulateItemController().process(categories_dict, json_eateries) + self.logger_wrapper(PopulateItemController(), "Populating items", [categories_dict, json_eateries]) - print("Done populating") \ No newline at end of file + print("Done populating") From 10ac7fed0b18fadc07ceaae8b0200bf64ec89e5e Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 7 Feb 2024 12:13:29 -0500 Subject: [PATCH 191/305] Update envrctemplate --- .envrctemplate | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.envrctemplate b/.envrctemplate index 76756c7..4b6fd4e 100644 --- a/.envrctemplate +++ b/.envrctemplate @@ -9,4 +9,5 @@ export POSTGRES_HOST= export POSTGRES_PORT= export VENDOR_API_KEY= export VENDOR_BEARER_TOKEN= -export REPORT_KEY= \ No newline at end of file +export REPORT_KEY= +export DJANGO_SECRET_KEY= \ No newline at end of file From e815c38dd0204f74034d3541eeec3a96e9337d22 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye Date: Wed, 7 Feb 2024 18:32:11 -0500 Subject: [PATCH 192/305] Run cronjob from 6am-6pm --- update_db.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update_db.txt b/update_db.txt index 3e68ead..5892eff 100644 --- a/update_db.txt +++ b/update_db.txt @@ -1 +1 @@ -0 8-21 * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /usr/app/cron_log.txt 2>&1; +0 11-23 * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /usr/app/cron_log.txt 2>&1; From c8ac347e20912b6c5045c313b853d2742f0ca007 Mon Sep 17 00:00:00 2001 From: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Date: Wed, 7 Feb 2024 20:11:58 -0500 Subject: [PATCH 193/305] Update deploy-dev.yml to use eatery-blue-dev --- .github/workflows/deploy-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index cfa768e..41679be 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -27,7 +27,7 @@ jobs: context: ./ file: ./Dockerfile push: true - tags: cornellappdev/eatery-blue:${{ steps.vars.outputs.sha_short }} + tags: cornellappdev/eatery-blue-dev:${{ steps.vars.outputs.sha_short }} - name: Remote SSH and Deploy uses: appleboy/ssh-action@master env: From 387e257d241ab27069424c8763d897c216653fbe Mon Sep 17 00:00:00 2001 From: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Date: Wed, 7 Feb 2024 20:13:01 -0500 Subject: [PATCH 194/305] Update deploy-prod.yml to use eatery-blue --- .github/workflows/deploy-prod.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 1021be4..23e3e41 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -21,6 +21,13 @@ jobs: - name: Get SHA id: vars run: echo "::set-output name=sha_short::$(git rev-parse --short release)" + - name: Docker Build & Push + uses: docker/build-push-action@v2 + with: + context: ./ + file: ./Dockerfile + push: true + tags: cornellappdev/eatery-blue:${{ steps.vars.outputs.sha_short }} - name: Remote SSH and Deploy uses: appleboy/ssh-action@master env: From 38f855f7565144d40fff7c3398edf8a71131d3cd Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 14 Feb 2024 17:03:33 -0500 Subject: [PATCH 195/305] Fixed upcoming menus timezone issue --- src/eatery/serializers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 0e70bb4..2f4e23a 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -2,6 +2,7 @@ from eatery.models import Eatery from event.models import Event from event.serializers import EventSerializer, EventSerializerSimple, EventReadSerializer +from django.utils import timezone from datetime import date, timedelta from time import mktime @@ -65,11 +66,11 @@ class EaterySerializerByDay(serializers.ModelSerializer): def get_events(self, obj): day = self.context.get("day") - today = date.today() + today = timezone.now().date() today = today + timedelta(days=day) end_today = today + timedelta(days=1) - today_unix = mktime(today.timetuple()) + 18000 - end_today_unix = mktime(end_today.timetuple()) + 18000 + today_unix = mktime(today.timetuple()) + end_today_unix = mktime(end_today.timetuple()) events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=end_today_unix) serializer = EventReadSerializer(instance=events, many=True) return serializer.data From 888fbf51c6317f6d1616f1e0ad0adac63347963c Mon Sep 17 00:00:00 2001 From: Mateo Weiner Date: Wed, 14 Feb 2024 17:30:16 -0500 Subject: [PATCH 196/305] caching (#80) --- src/eatery/views.py | 10 +++++----- src/eatery_blue_backend/settings.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/eatery/views.py b/src/eatery/views.py index 347782f..d775f75 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -2,15 +2,14 @@ from eatery.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse from django.shortcuts import get_object_or_404 +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import viewsets from .permissions import EateryPermission - from eatery.datatype.Eatery import EateryID - from eatery.models import Eatery - from .controllers.update_eatery import UpdateEateryController class EateryViewSet(viewsets.ModelViewSet): @@ -25,13 +24,13 @@ def retrieve(self, request, *args, **kwargs): instance = self.get_object() serializer = EaterySerializer(instance) return Response(serializer.data) - + + @method_decorator(cache_page(60*60*2)) # cache for 2 hours def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) serializer = EaterySerializer(queryset, many=True) return Response(serializer.data) - def get_object(self): queryset = self.filter_queryset(self.get_queryset()) lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field @@ -97,6 +96,7 @@ class GetEateriesByDay(APIView): """ Get all eatery information by day """ + @method_decorator(cache_page(60*60*2)) # cache for 2 hours def get(self, request, day): eateries = EaterySerializerByDay(Eatery.objects.exclude(events__event_description="Open"), many=True, context={"day": day}) return Response(eateries.data) \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index e25556b..9894ce4 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -25,7 +25,7 @@ # SECURITY WARNING: don't run with debug turned on in production! # IS_PROD can be None -DEBUG = False if os.environ.get("IS_PROD") else True +DEBUG = False if os.environ.get("IS_PROD") is True else True ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS").split(",") From 808ed7e60db7f8bccc7502d1571f8fae39b47633 Mon Sep 17 00:00:00 2001 From: Thomas Vignos <113550626+tjvignos@users.noreply.github.com> Date: Wed, 14 Feb 2024 17:55:49 -0500 Subject: [PATCH 197/305] Update README.md (#81) --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 7389d9f..c58c772 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,16 @@ This is the backend for eatery-blue-backend. - To set up the tables and data, make sure current working directory is the `src` folder and run `bash reset_db.sh` - To run the backend, run `bash start.sh` +## SP24 Members + +- Thomas Vignos +- Aayush Agnihotri + +## FA23 Members + +- Mateo Weiner +- Thomas Vignos + ## SP23 Members - Mateo Weiner From ce8c8bd4f800e849fefa39fc06fc65329a3719c7 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Fri, 16 Feb 2024 09:01:05 -0500 Subject: [PATCH 198/305] Fix timezone issue again --- src/eatery/serializers.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 2f4e23a..f4eacaf 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -66,12 +66,9 @@ class EaterySerializerByDay(serializers.ModelSerializer): def get_events(self, obj): day = self.context.get("day") - today = timezone.now().date() - today = today + timedelta(days=day) - end_today = today + timedelta(days=1) - today_unix = mktime(today.timetuple()) - end_today_unix = mktime(end_today.timetuple()) - events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=end_today_unix) + today = (timezone.now() - timedelta(hours=5)).date() + timedelta(days=day) + today_unix = mktime(today.timetuple()) + 18000 + events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=today_unix + 86400) serializer = EventReadSerializer(instance=events, many=True) return serializer.data From c4349a2b18188981bd0560439bb5c507a4962fef Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Fri, 16 Feb 2024 10:52:19 -0500 Subject: [PATCH 199/305] Removed magic numbers --- src/eatery/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index f4eacaf..e207927 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -67,8 +67,8 @@ class EaterySerializerByDay(serializers.ModelSerializer): def get_events(self, obj): day = self.context.get("day") today = (timezone.now() - timedelta(hours=5)).date() + timedelta(days=day) - today_unix = mktime(today.timetuple()) + 18000 - events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=today_unix + 86400) + today_unix = mktime(today.timetuple()) + timedelta(hours=5).seconds + events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=today_unix + timedelta(days=1).seconds) serializer = EventReadSerializer(instance=events, many=True) return serializer.data From aa299bf795b1876ed4fe9a5417f1457300b23748 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 21 Feb 2024 18:19:29 -0500 Subject: [PATCH 200/305] Small by day bug fix --- src/eatery/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index e207927..bf56ef1 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -68,7 +68,7 @@ def get_events(self, obj): day = self.context.get("day") today = (timezone.now() - timedelta(hours=5)).date() + timedelta(days=day) today_unix = mktime(today.timetuple()) + timedelta(hours=5).seconds - events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=today_unix + timedelta(days=1).seconds) + events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=today_unix + timedelta(days=1).total_seconds()) serializer = EventReadSerializer(instance=events, many=True) return serializer.data From 87779869e703038b8e73b2c8340b87fa24bc4308 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 22 Feb 2024 12:26:38 -0500 Subject: [PATCH 201/305] Removing alert model --- src/alert/admin.py | 4 ---- src/alert/apps.py | 6 ------ src/alert/migrations/0001_initial.py | 26 -------------------------- src/alert/migrations/__init__.py | 0 src/alert/models.py | 14 -------------- src/alert/permissions.py | 13 ------------- src/alert/serializers.py | 8 -------- src/alert/urls.py | 10 ---------- src/alert/views.py | 9 --------- src/eatery_blue_backend/settings.py | 1 - src/eatery_blue_backend/urls.py | 1 - 11 files changed, 92 deletions(-) delete mode 100644 src/alert/admin.py delete mode 100644 src/alert/apps.py delete mode 100644 src/alert/migrations/0001_initial.py delete mode 100644 src/alert/migrations/__init__.py delete mode 100644 src/alert/models.py delete mode 100644 src/alert/permissions.py delete mode 100644 src/alert/serializers.py delete mode 100644 src/alert/urls.py delete mode 100644 src/alert/views.py diff --git a/src/alert/admin.py b/src/alert/admin.py deleted file mode 100644 index 707c401..0000000 --- a/src/alert/admin.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.contrib import admin -from alert.models import Alert - -admin.site.register(Alert) diff --git a/src/alert/apps.py b/src/alert/apps.py deleted file mode 100644 index 7857426..0000000 --- a/src/alert/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class AlertConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = "alert" \ No newline at end of file diff --git a/src/alert/migrations/0001_initial.py b/src/alert/migrations/0001_initial.py deleted file mode 100644 index 46b6e73..0000000 --- a/src/alert/migrations/0001_initial.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 4.0 on 2023-04-13 00:10 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('eatery', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Alert', - fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('description', models.CharField(max_length=250)), - ('start_timestamp', models.IntegerField()), - ('end_timestamp', models.IntegerField()), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='eatery.eatery')), - ], - ), - ] diff --git a/src/alert/migrations/__init__.py b/src/alert/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/alert/models.py b/src/alert/models.py deleted file mode 100644 index 4639bb8..0000000 --- a/src/alert/models.py +++ /dev/null @@ -1,14 +0,0 @@ -from django.db import models -from eatery.models import Eatery - - -class Alert(models.Model): - id = models.IntegerField(primary_key=True) - eatery = models.ForeignKey(Eatery, on_delete=models.DO_NOTHING) - description = models.CharField(max_length = 250) - start_timestamp = models.IntegerField() - end_timestamp = models.IntegerField() - - -def __str__(self): - return self.description \ No newline at end of file diff --git a/src/alert/permissions.py b/src/alert/permissions.py deleted file mode 100644 index 6e50db2..0000000 --- a/src/alert/permissions.py +++ /dev/null @@ -1,13 +0,0 @@ -from rest_framework import permissions - -class AlertPermission(permissions.BasePermission): - - def has_permission(self, request, view): - if view.action in ['list', 'retrieve']: - return True - return request.user.is_staff - - def has_object_permission(self, request, view, obj): - if view.action in ['retrieve']: - return True - return request.user.is_staff \ No newline at end of file diff --git a/src/alert/serializers.py b/src/alert/serializers.py deleted file mode 100644 index 657bdc1..0000000 --- a/src/alert/serializers.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import serializers - -from alert.models import Alert - -class AlertSerializer(serializers.ModelSerializer): - class Meta: - model = Alert - fields = ['id', 'description', 'start_timestamp', 'end_timestamp'] diff --git a/src/alert/urls.py b/src/alert/urls.py deleted file mode 100644 index d1b575c..0000000 --- a/src/alert/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path, include -from rest_framework.routers import DefaultRouter -from alert import views - -router = DefaultRouter() -router.register(r'', views.AlertViewSet) - -urlpatterns = [ - path('', include(router.urls)) -] \ No newline at end of file diff --git a/src/alert/views.py b/src/alert/views.py deleted file mode 100644 index 0c1ad5f..0000000 --- a/src/alert/views.py +++ /dev/null @@ -1,9 +0,0 @@ -from rest_framework import viewsets -from alert.models import Alert -from alert.serializers import AlertSerializer -from .permissions import AlertPermission - -class AlertViewSet(viewsets.ModelViewSet): - queryset = Alert.objects.all() - serializer_class = AlertSerializer - permission_classes = [AlertPermission] \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 9894ce4..685cf6f 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -43,7 +43,6 @@ "rest_framework", "eatery_blue_backend", "eatery", - "alert", "event", "report", "item", diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 2909e69..e943338 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -20,6 +20,5 @@ path("admin/", admin.site.urls), path("eatery/", include("eatery.urls")), path("report/", include("report.urls")), - path("alert/", include("alert.urls")), path("person/", include("person.urls")), ] From 814704ff2cace15a2b36276d9ab8eb9e7a4b8620 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 22 Feb 2024 12:32:23 -0500 Subject: [PATCH 202/305] Removing unnecessary static source --- src/static_sources/cornell_dining_now_eateries.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/static_sources/cornell_dining_now_eateries.json diff --git a/src/static_sources/cornell_dining_now_eateries.json b/src/static_sources/cornell_dining_now_eateries.json deleted file mode 100644 index 2d2ac1b..0000000 --- a/src/static_sources/cornell_dining_now_eateries.json +++ /dev/null @@ -1 +0,0 @@ -{"status":"success","data":{"eateries":[{"id":31,"slug":"104-West","name":"104West!","nameshort":"104West!","about":"
\r\nLocated next to the Center for Jewish Living building on the south edge of west campus, 104West!<\/strong> is Cornell's kosher and multicultural
dining room<\/a>. Menus are prepared under the supervision of STAR-K (meat and pareve) and STAR-D (dairy) Kosher Certifications, and Jewish dietary laws are strictly followed with the direction of a resident \"Mashgiach,\" or kosher-food supervisor.\r\n

\r\nYou don't have to keep kosher to enjoy the menu\u2013come sample traditional kosher entrees and enjoy mouth-watering ethnic and international options with a kosher flair. Dining options also include Halal, Seventh-day Adventist, vegetarian, vegan, and other diets.\r\n

\r\nShabbat dinner times vary over the course of the year, based on sunset times. Save money and help us plan by making advance reservations for Shabbat dinners and holiday meals at
https:\/\/kosher.scl.cornell.edu<\/a>!\r\n

\r\nMore Info:
104West!<\/strong><\/a>\r\n

\r\n
\"104West!<\/a>\r\n

","aboutshort":"Cornell's kosher and multicultural dining room is STAR-K and STAR-D certified.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"vlpa2hk9677m9bcbh6n2dtpn7k@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-272-6907","contactEmail":null,"serviceUnitId":9,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.444266,"longitude":-76.487598,"location":"104 West Avenue","coordinates":{"latitude":42.444266,"longitude":-76.487598},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":7,"slug":"Amit-Bhatia-Libe-Cafe","name":"Amit Bhatia Libe Caf\u00e9","nameshort":"Amit Bhatia Libe Caf\u00e9","about":"
A combined effort between Cornell Dining and Olin Library, the Amit Bhatia Libe Caf\u00e9<\/strong> serves specialty Starbucks coffees, smoothies, pastries, and Freshtake Grab-n-Go sandwiches, salads, and snacks.\r\n

\r\n\r\nMore Info:
Amit Bhatia Libe Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Amit<\/a>\r\n

","aboutshort":"The perfect place to take a study break, or to enjoy a latte and a pastry while you enjoy wireless Internet access on your laptop.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"g1pfs9edl1ks5o2dbc58e7fhm8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-4344","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448019,"longitude":-76.484499,"location":"Olin Library","coordinates":{"latitude":42.448019,"longitude":-76.484499},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640124000,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640210400,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640296800,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":8,"slug":"Atrium-Cafe","name":"Atrium Caf\u00e9","nameshort":"Atrium Caf\u00e9","about":"
The Atrium Caf\u00e9's<\/strong> coffee kiosk proudly serves Starbucks specialty coffee, pastries, and Grab-n-Go items, with extended hours. When class is in session, the coffee kiosk opens weekdays at 7am, and closes at 4pm Monday through Thursday, and 2pm Friday.\r\n

\r\nMore Info:
Atrium Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Atrium<\/a>\r\n

","aboutshort":"The Atrium Caf\u00e9 is located in historic Sage Hall, home to the Johnson Graduate School of Management. Coffee kiosk is open extended hours!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"9g3c81c0p2loacsbvrjj5o371c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7591","contactEmail":null,"serviceUnitId":9999,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.445784,"longitude":-76.483232,"location":"Sage Hall","coordinates":{"latitude":42.445784,"longitude":-76.483232},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":1,"slug":"Bear-Necessities","name":"Bear Necessities Grill & C-Store","nameshort":"Bear Necessities","about":"
The grill serves up deluxe burgers, hot chicken sandwiches, delicious french fries, and other mouth-watering comfort foods. You can also order 5 Star Subs and homemade pizza. Bear Necessities<\/strong> also has a convenience store with staple food, beverages and household items.\r\n

\r\n\r\nMore Info:
Bear Necessities<\/strong><\/a>\r\n

\r\n
\"Bear<\/a>\r\n

","aboutshort":"Bear Necessities is a convenience store and grill located on the first floor of Robert Purcell Community Center on north campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h319a8fk4b5lv0644ebkskhha8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-8227","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455777,"longitude":-76.477657,"location":"RPCC - first floor","coordinates":{"latitude":42.455777,"longitude":-76.477657},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640134800,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640221200,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Convenience Store","descrshort":"convenience store"}],"diningCuisines":[{"name":"Pizza","nameshort":"Pizza","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Grill - 5-Star Subs","category":"Grill","item":"5-Star Subs","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Pizza","category":"Pasta & Pizza","item":"Pizza","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Calzones","category":"Pasta & Pizza","item":"Calzones","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Misc - Mozzarella Sticks","category":"Misc","item":"Mozzarella Sticks","healthy":false,"showCategory":false},{"descr":"Misc - Onion Petals","category":"Misc","item":"Onion Petals","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":25,"slug":"Becker-House-Dining","name":"Becker House Dining Room","nameshort":"Becker House Dining","about":"
\r\nThe Becker House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
Becker House Dining Room<\/strong><\/a>\r\n

\r\n
\"Becker<\/a>\r\n

","aboutshort":"Dining room located in Carl Becker House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"di2s9rofto7m8innt5e8vftl0o@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8882","contactEmail":null,"serviceUnitId":2,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.448336,"longitude":-76.489606,"location":"Carl Becker House","coordinates":{"latitude":42.448336,"longitude":-76.489606},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":10,"slug":"Big-Red-Barn","name":"Big Red Barn","nameshort":"Big Red Barn","about":"
\r\nThe Big Red Barn<\/strong> is a cozy caf\u00e9 with a delicious breakfast and lunch menu. It also serves as Cornell's on-campus social center for graduate and professional students. Relax at outdoor picnic tables, or sit inside by the fireplace in comfortable lounge furniture.\r\n

\r\nMore Info:
Big Red Barn<\/strong><\/a>\r\n

\r\n
\"Big<\/a>\r\n

","aboutshort":"Once a carriage house, the Big Red Barn is now a cozy caf\u00e9 with a delicious breakfast and lunch menu.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"u1kmovdep2qlmr86io8h4p3ee8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0428","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448526,"longitude":-76.48098,"location":"Big Red Barn","coordinates":{"latitude":42.448526,"longitude":-76.48098},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":11,"slug":"Bug-Stop-Bagels","name":"Bus Stop Bagels","nameshort":"Bus Stop Bagels","about":"
\r\nBus Stop Bagels<\/strong>, next to the Trillium food court in Kennedy Hall, gets fresh bagels delivered twice a day from Ithaca Bakery with all the usual toppings and a great selection of house sandwiches, plus Starbucks coffee drinks and snack foods.\r\n

\r\nMore Info:
Bus Stop Bagels<\/strong><\/a>\r\n

\r\n
\"Bus<\/a>\r\n

","aboutshort":"Bagels for breakfast, bagels for lunch, bagels to go!","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"rpp0nrlp282t9h18hhol5f0dkc@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447839,"longitude":-76.479456,"location":"Kennedy Hall","coordinates":{"latitude":42.447839,"longitude":-76.479456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640116800,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640203200,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640289600,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Coffee Bar - 96oz Coffee2Go","category":"Coffee Bar","item":"96oz Coffee2Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":12,"slug":"Cafe-Jennie","name":"Caf\u00e9 Jennie","nameshort":"Caf\u00e9 Jennie","about":"
\r\nLocated in The Cornell Store, Caf\u00e9 Jennie is named for Jennie McGraw, the daughter of John McGraw, a wealthy industrialist and a founding Cornell Trustee. Stop by for a Peet's Coffee, delicious Cheesecake Factory baked goods, and a mouth-watering array of sandwiches and wraps.\r\n

\r\nMore Info:
Caf\u00e9 Jennie<\/strong><\/a>\r\n

\r\n
\"Caf\u00e9<\/a>\r\n

","aboutshort":"A caf\u00e9 and sandwich\/pastry shop located in The Cornell Store.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"geron0aq1ooj7jugmcmdc2s2cc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8095","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446851,"longitude":-76.484376,"location":"The Cornell Store","coordinates":{"latitude":42.446851,"longitude":-76.484376},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640098800,"endTimestamp":1640120400,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640185200,"endTimestamp":1640206800,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640271600,"endTimestamp":1640286000,"start":"10:00am","end":"2:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Javiva","category":"Coffee Bar","item":"Javiva","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":2,"slug":"Carols-Cafe","name":"Carol's Caf\u00e9","nameshort":"Carol's Caf\u00e9","about":"
\r\nCome visit Carol's Caf\u00e9<\/strong> and enjoy a menu featuring specialty coffee from Starbucks, smoothies, hot daily soup selections, sushi, fresh fruit and snacks, Freshtake Grab-n-Go sandwiches and salads, delicious pastries and baked goods and more.\r\n

\r\nMore Info:
Carol's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Carol's<\/a>\r\n

","aboutshort":"Warm, inviting caf\u00e9 at Carol Tatkon Center open to the whole campus community.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"05r2nhfjbnknmsccgd6u8dij0g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2257","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453236,"longitude":-76.479404,"location":"Carol Tatkon Center, Balch Hall","coordinates":{"latitude":42.453236,"longitude":-76.479404},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":26,"slug":"Cook-House-Dining","name":"Cook House Dining Room","nameshort":"Cook House Dining","about":"
\r\nThe Cook House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. The eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Cook House Dining Room<\/strong><\/a>\r\n

\r\n
\"Cook<\/a>\r\n

","aboutshort":"Dining room located in Alice Cook House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"27hli58rto1hpf15m3sbe54sak@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-9508","contactEmail":null,"serviceUnitId":1,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.44889,"longitude":-76.488919,"location":"Alice Cook House","coordinates":{"latitude":42.44889,"longitude":-76.488919},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":14,"slug":"Cornell-Dairy-Bar","name":"Cornell Dairy Bar","nameshort":"Cornell Dairy Bar","about":"
\r\nIn the beautifully renovated Stocking Hall on the east end of Tower Road, the Cornell Dairy Bar is a great place for breakfast, coffee, or even sweet treat like Cornell ice cream, sundaes and floats. Regular hours vary seasonally, especially weekend hours, so please check the specific hours on this page for updates.\r\n

\r\nMore Info:
Cornell Dairy Bar<\/strong><\/a>\r\n

\r\n
\"Cornell<\/a>\r\n

","aboutshort":"In the renovated Stocking Hall, the Cornell Dairy features a variety of delicious Cornell Dairy ice cream, sundaes, and floats.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"prvu4v0nr4eu94mqu9q7busa6g@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7660","contactEmail":"dairybar@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447301,"longitude":-76.471398,"location":"Stocking Hall","coordinates":{"latitude":42.447301,"longitude":-76.471398},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"icecream-pink"},{"id":41,"slug":"Crossings-Cafe","name":"Crossings Caf\u00e9","nameshort":"Crossings Caf\u00e9","about":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","aboutshort":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"loadevpceh49fl3c8cuheb2dvk@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.4558,"longitude":-76.479386,"location":"Toni Morrison Hall","coordinates":{"latitude":42.4558,"longitude":-76.479386},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640124000,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640210400,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Mexican - Quesadillas","category":"Mexican","item":"Quesadillas","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Peet's Coffee","category":"Coffee Bar","item":"Peet's Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":32,"slug":"frannys","name":"Franny's","nameshort":"Franny's","about":"One of Cornell's newest eateries is Franny's, a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls. You'll find a unique mix of Pan-Asian sandwiches, rice bowls and ramen, bao buns and fries, as well as refreshing Asian-inspired drinks.","aboutshort":"Franny's is a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls, featuring a unique mix of Asian-inspired cuisine.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekdays","googleCalendarId":"cornell.edu_26ui0ai54lp0cdp2m3j27ci86c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0293","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.451053,"longitude":-76.483884,"location":"Next to Sibley Hall","coordinates":{"latitude":42.451053,"longitude":-76.483884},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[{"name":"Asian","nameshort":"Asian","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Asian - Rice Bowls","category":"Asian","item":"Rice Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Noodle Bowls","category":"Asian","item":"Noodle Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Banh Mi Sandwiches","category":"Asian","item":"Banh Mi Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":16,"slug":"Goldies-Cafe","name":"Goldie's Caf\u00e9","nameshort":"Goldie's Caf\u00e9","about":"
\r\nGoldie's is a great location on Central Campus for breakfast, lunch, or a mid-day snack. Signature sandwiches \u2013 some served on German-style pretzel rolls \u2013 have become customer favorites, and a wide array of Freshtake Grab-n-Go items, snacks, and desserts are always on the menu.\r\n

\r\nMore Info:
Goldie's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Goldie's<\/a>\r\n

","aboutshort":"Conveniently located in the Physical Sciences Building on Central Campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"kb9ce5jj2f6oli3c90tc7j6peo@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-6775","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450064,"longitude":-76.481503,"location":"Physical Sciences Building","coordinates":{"latitude":42.450064,"longitude":-76.481503},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":15,"slug":"Green-Dragon","name":"Green Dragon","nameshort":"Green Dragon","about":"
\r\nStop by the Green Dragon<\/strong> and enjoy a hot specialty Finger Lakes Coffee Roasters coffee, Freshtake Grab-n-Go sandwiches and salads, kosher items, and delicious desserts.\r\n

\r\nMore Info:
Green Dragon<\/strong><\/a>\r\n

\r\n
\"Green<\/a>\r\n

","aboutshort":"A hot spot on central campus, especially for the students, faculty, and staff of College of Architecture, Art and Planning.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"7sii70faon9ta2vpoehr69415s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3327","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450948,"longitude":-76.484456,"location":"Sibley Hall","coordinates":{"latitude":42.450948,"longitude":-76.484456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":24,"slug":"Hot-Dog-Cart","name":"Hot Dog Cart","nameshort":"Hot Dog Cart","about":"
\r\nCornell Dining's Hot Dog Cart<\/strong> is usually open outside Day Hall on summer weekdays when weather permits. On special occasions, the Hot Dog Cart may be elsewhere on campus. Keep an eye on Cornell Dining's
Facebook<\/a> and Twitter<\/a> for updates.\r\n

\r\nMore Info:
Hot Dog Cart<\/strong><\/a>\r\n

\r\n
\"Hot<\/a>\r\n

","aboutshort":"Enjoy lunch al fresco when weather permits, choosing an all-beef hot dog or vegetarian (tofu) hot dog.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"11:00am - 2:00pm weekdays, weather permitting","googleCalendarId":"cornell.edu_eaq3euadrebh0dmgqt618l7tgs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":34,"slug":"icecreamcart","name":"Ice Cream Bike","nameshort":"Ice Cream Bike","about":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","aboutshort":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekday middays for the summer, and Friday evenings for Cornell's concert series.","googleCalendarId":"cornell.edu_3kuppj4nsjes2b42jhpno5u97g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"icecream-pink"},{"id":27,"slug":"Jansens-Dining","name":"Jansen's Dining Room at Bethe House","nameshort":"Jansen's Dining","about":"
\r\nJansen's Dining Room at Bethe House<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Chef Jacob Kuehn puts together exciting new menus throughout the year. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Jansen's Dining Room at Bethe House<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Dining room located in Hans Bethe House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h0nfohf0d90ot1rmukjphj7ajc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1736","contactEmail":null,"serviceUnitId":5,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447116,"longitude":-76.48864,"location":"Hans Bethe House","coordinates":{"latitude":42.447116,"longitude":-76.48864},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":28,"slug":"Jansens-Market","name":"Jansen's Market","nameshort":"Jansen's Market","about":"
\r\nIn addition to an array of snacks, beverages, fresh and frozen take-away meals, household items, and pharmacy and beauty supplies, Jansen's Market<\/strong> also serve Starbucks coffee, bubble tea, smoothies, pastries, frozen yogurt, and Dreamfactory cheesecake.\r\n

\r\nMore Info:
Jansen's Market<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Full-service convenience store located on the first floor of Noyes Community Recreation Center on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"0dqnc6l2mt25okch8nimsnojhg@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-4997","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446325,"longitude":-76.487932,"location":"Noyes Community Recreation Center","coordinates":{"latitude":42.446325,"longitude":-76.487932},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Convenience Store","descrshort":"convenience store"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Bubble Tea","category":"Coffee Bar","item":"Bubble Tea","healthy":false,"showCategory":false},{"descr":"Misc - Peanut Butter Sandwich Bar","category":"Misc","item":"Peanut Butter Sandwich Bar","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":29,"slug":"Keeton-House-Dining","name":"Keeton House Dining Room","nameshort":"Keeton House Dining","about":"
\r\nThe Keeton House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Sample Chef Nery's unique menu offerings in the entr\u00e9e station, Asian station, grill, deli, and salad bar. This eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Keeton House Dining Room<\/strong><\/a>\r\n

\r\n
\"Keeton<\/a>\r\n

","aboutshort":"Dining room located in William Keeton House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ekd72jfc2qai617oloa2b0ibp0@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3033","contactEmail":null,"serviceUnitId":3,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446942,"longitude":-76.489992,"location":"William Keeton House","coordinates":{"latitude":42.446942,"longitude":-76.489992},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":42,"slug":"Mann-Cafe","name":"Mann Caf\u00e9","nameshort":"Mann Caf\u00e9","about":"Take a study break at Mann Caf\u00e9!","aboutshort":"Take a study break at Mann Caf\u00e9!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ppbukfcjo05629gk0t4fu26aro@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448799,"longitude":-76.47851,"location":"Mann Library on the Ag Quad","coordinates":{"latitude":42.448799,"longitude":-76.47851},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Mexican - Burritos","category":"Mexican","item":"Burritos","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":18,"slug":"Marthas-Cafe","name":"Martha's Caf\u00e9","nameshort":"Martha's Cafe","about":"
\r\nMartha's Caf\u00e9<\/strong>, in Martha Van Rensselaer Hall, offers made-to-order Mediterranean-inspired grain and salad bowls and wraps, composed bowls, Copper Horse Coffee, and breakfast!\r\n

\r\nMore Info:
Martha's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Martha's<\/a>\r\n

","aboutshort":"Fresh food with a Mediterranean flair.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sperf092mrbt796rr36toeqrus@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-8080","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450115,"longitude":-76.479237,"location":"Martha Van Rensselaer Hall","coordinates":{"latitude":42.450115,"longitude":-76.479237},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Milk","category":"Cornell Dairy","item":"Milk","healthy":false,"showCategory":true},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false},{"descr":"Lunch - Hot and Cold Mediterranean Bowls","category":"Lunch","item":"Hot and Cold Mediterranean Bowls","healthy":true,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":19,"slug":"Mattins-Cafe","name":"Mattin's Caf\u00e9","nameshort":"Mattin's Caf\u00e9","about":"
\r\nYou\u2019ll find Mattin's Caf\u00e9<\/strong> in the atrium of Duffield Hall, adjacent to Phillips Hall on the Engineering Quad. Enjoy made-to-order deli sandwiches, boneless wings, hot Starbucks coffee, Freshtake Grab-n-Go items, soups, kosher items, mouth-watering pastries and more.\r\n

\r\nMore Info:
Mattin's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Mattin's<\/a>\r\n

","aboutshort":"Popular with engineers and a go to spot in the stunning atrium of Duffield Hall.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"1qman2n728pqjuq5ntaoofc7v0@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-4581","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.444162,"longitude":-76.482287,"location":"Duffield Hall","coordinates":{"latitude":42.444162,"longitude":-76.482287},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640113200,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640199600,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":33,"slug":"mccormicks","name":"McCormick's at Moakley House","nameshort":"McCormick's","about":"McCormick's at Moakley House offers a casual environment close to campus, open daily through the season. Treat a campus visitor to an impeccably served luncheon, stop for refreshments after a round of golf, meet coworkers at the end of the day for a burger and a beer, or bring the family for a relaxing weekend brunch.\r\n\r\nNamed for Jack McCormick, a varsity golfer from the Cornell Class of 1957 whose will included a bequest to modernize Moakley House, this eatery alongside the award-winning Robert Trent Jones Golf Course at Cornell University isn't just for golfers. It's open to the public and has plenty of parking, and we welcome everyone for a drink, a snack, or a meal.\r\n\r\nMcCormick's is closed for the 2021 season as of September 24th.\r\n\r\n

\r\nMore Info:
McCormick's at Moakley House<\/strong><\/a>\r\n

","aboutshort":"Named for Jack McCormick, a varsity golfer from the Cornell Class of 1957, McCormick's is open to the public and has plenty of parking.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"cornell.edu_gqlmrg7n0qk8qihl75ru2u1p80@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-6536","contactEmail":"mccormicks@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.458324,"longitude":-76.469539,"location":"Robert Trent Jones Golf Course","coordinates":{"latitude":42.458324,"longitude":-76.469539},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false}],"announcements":[{"id":199,"title":"McCormick's is closed for the 2021 season after Friday, September 24th.","announceType":"EATERY","startTimestamp":1632283200,"stopTimestamp":1640408340}],"icon":"coffee-brown"},{"id":3,"slug":"North-Star","name":"North Star Dining Room","nameshort":"North Star","about":"
\r\nThe North Star Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Enjoy an open kitchen concept and numerous unique food stations serving a huge variety of ethnic and regional American cuisine. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
North Star Dining Room<\/strong><\/a>\r\n

\r\n
\"North<\/a>\r\n

","aboutshort":"Dining room located in Appel Commons on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ecbhqf3ibeei09dds91viod5g8@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2992","contactEmail":null,"serviceUnitId":7,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453527,"longitude":-76.475944,"location":"Appel Commons, Third floor","coordinates":{"latitude":42.453527,"longitude":-76.475944},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[{"id":216,"title":"North Star Dining Room is closed for renovation through the Spring 2022 semester!","announceType":"EATERY","startTimestamp":1639976400,"stopTimestamp":1654055940}],"icon":"restaurant-red"},{"id":20,"slug":"Okenshields","name":"Okenshields","nameshort":"Okenshields","about":"
\r\nOkenshields<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. With hundreds of menu options to choose from \u2013 from an Asian station to a healthy foods bar featuring whole grain salads and cut fruit \u2013 you're sure to find something that meets your fancy.\r\n

\r\nMore Info:
Okenshields<\/strong><\/a>\r\n

\r\n
\"Okenshields<\/a>\r\n

","aboutshort":"Dining room located in Willard Straight Hall on Central Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"3hku0mr66kapq1lh8fakug9kko@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6636","contactEmail":null,"serviceUnitId":10,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446491,"longitude":-76.485678,"location":"Willard Straight Hall","coordinates":{"latitude":42.446491,"longitude":-76.485678},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":4,"slug":"Risley-Dining","name":"Risley Dining Room","nameshort":"Risley Dining Room","about":"
\r\nRisley Dining Room<\/strong> is a 100% gluten-free, tree-nut-free, and peanut-free
dining room<\/a> for house residents, and for the entire Cornell community. There are always vegan and vegetarian choices. Modeled after the Christchurch Refectory at Oxford University, the dining room maintains the same Gothic charm as when it first opened as the all-Ivy \"Risley Great Hall\" in 1913. \r\n

\r\nMore Info:
Risley Dining Room<\/strong><\/a>\r\n

\r\n
\"Risley<\/a>\r\n

","aboutshort":"Dining room located in Risley Residential College on North Campus.","nutrition":"Risley Dining Room is certified 100% gluten-free, tree-nut-free, and peanut-free. Thank you for not bringing outside food into Risley!<\/strong>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"hq98btd396f3077p88d30c84fs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-4229","contactEmail":null,"serviceUnitId":8,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453117,"longitude":-76.481946,"location":"Risley Residential College","coordinates":{"latitude":42.453117,"longitude":-76.481946},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":5,"slug":"RPCC-Marketplace","name":"Robert Purcell Marketplace Eatery","nameshort":"RPCC Marketplace","about":"
\r\nThe Robert Purcell Marketplace Eatery<\/strong> is an award-winning
dining room<\/a> with a huge variety of menu options. Chef Kevin Moore, who has been with Cornell Dining for over 25 years, most recently at 104West!<\/a>, plans creative menus with roots in a variety of international traditions. Note: Our Late Dinner hours feature a limited menu.\r\n

\r\nMore Info:
Robert Purcell Marketplace Eatery<\/strong><\/a>\r\n

\r\n
\"Robert<\/a>\r\n

","aboutshort":"Dining room located in Robert Purcell Community Center on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"32uglqeiqfo9edhpp4tka8oqsc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1138","contactEmail":null,"serviceUnitId":6,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455973,"longitude":-76.477354,"location":"RPCC, Third floor","coordinates":{"latitude":42.455973,"longitude":-76.477354},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640088000,"endTimestamp":1640100600,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":2,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Pork Breakfast Sausage","healthy":false,"sortIdx":3},{"item":"Home Fries","healthy":true,"sortIdx":4},{"item":"Pancakes with Syrup","healthy":false,"sortIdx":5},{"item":"French Toast Sticks","healthy":false,"sortIdx":6},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":7}]},{"category":"Grill Station","sortIdx":3,"items":[{"item":"Scrambled Tofu","healthy":true,"sortIdx":1},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":2}]},{"category":"Specialty Station","sortIdx":4,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":2},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":3},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":4},{"item":"Waffle Bar","healthy":false,"sortIdx":5},{"item":"Assorted Cereal","healthy":false,"sortIdx":6},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":7}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640100600,"endTimestamp":1640115000,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":5,"items":[{"item":"Beef Vegetable Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":6,"items":[{"item":"Salad Bar","healthy":true,"sortIdx":1},{"item":"Grains For Brains","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":7,"items":[{"item":"Pasta Primavera","healthy":false,"sortIdx":1},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":8,"items":[{"item":"Potato Tots","healthy":false,"sortIdx":1},{"item":"Calabacitas","healthy":true,"sortIdx":2},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":9,"items":[{"item":"Hacienda Cheese Quesadilla","healthy":false,"sortIdx":1},{"item":"Grilled Cheese Sandwich","healthy":false,"sortIdx":2}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640124000,"endTimestamp":1640133000,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":10,"items":[{"item":"Cornell Chicken Noodle Soup","healthy":false,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":11,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2},{"item":"Grains For Brains","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":12,"items":[{"item":"Salmon","healthy":false,"sortIdx":1},{"item":"Spanish Style Brown Rice & Beans","healthy":true,"sortIdx":2},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Sides","sortIdx":13,"items":[{"item":"Steamed Vegetable Melange","healthy":true,"sortIdx":1},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":2},{"item":"Sauteed Zucchini","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4},{"item":"Fried Potato Puffs","healthy":false,"sortIdx":5}]},{"category":"Grill Station","sortIdx":14,"items":[{"item":"BBQ Chicken Pizza","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640174400,"endTimestamp":1640187000,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":15,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Turkey Breakfast Sausage","healthy":true,"sortIdx":3},{"item":"Bacon","healthy":false,"sortIdx":4},{"item":"Steamed Brown Rice","healthy":true,"sortIdx":5},{"item":"Home Fries","healthy":true,"sortIdx":6}]},{"category":"Grill Station","sortIdx":16,"items":[{"item":"Bacon & Cheese Omelet","healthy":false,"sortIdx":1},{"item":"Steamed Fresh Vegetables","healthy":true,"sortIdx":2},{"item":"Western Scrambled Tofu","healthy":true,"sortIdx":3},{"item":"Build Your Own Omelette","healthy":true,"sortIdx":4},{"item":"Cheese Omelet","healthy":false,"sortIdx":5}]},{"category":"Specialty Station","sortIdx":17,"items":[{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":1},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":2},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":3},{"item":"Waffle Bar","healthy":false,"sortIdx":4},{"item":"Assorted Cereal","healthy":false,"sortIdx":5},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":6}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640187000,"endTimestamp":1640201400,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":18,"items":[{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":19,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Salad Bar","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":20,"items":[{"item":"Pasta with Sun-dried Tomato Basil & Feta","healthy":true,"sortIdx":1},{"item":"Kale Pesto Chicken","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":21,"items":[{"item":"Chef's Choice Seasonal Vegetable","healthy":true,"sortIdx":1},{"item":"Seared Kale","healthy":true,"sortIdx":2},{"item":"Rosemary Roasted Red Skin Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":22,"items":[{"item":"Grilled Chicken Breast","healthy":true,"sortIdx":1},{"item":"Grilled Hot Dogs","healthy":false,"sortIdx":2},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4}]},{"category":"Pizza Station","sortIdx":23,"items":[{"item":"Supreme Vegetable Pizza","healthy":false,"sortIdx":1},{"item":"White Garlic Hawaiian Pizza","healthy":false,"sortIdx":2}]},{"category":"Wok\/Asian Station","sortIdx":24,"items":[{"item":"Moo Goo Gai Pan","healthy":false,"sortIdx":1},{"item":"Vegetable Fried Rice","healthy":false,"sortIdx":2},{"item":"Long Grain Brown Rice","healthy":true,"sortIdx":3},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":4}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640210400,"endTimestamp":1640219400,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":25,"items":[{"item":"Cornell Cream of Mushroom Soup","healthy":false,"sortIdx":1},{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":2}]},{"category":"Salad Bar Station","sortIdx":26,"items":[{"item":"Grains For Brains","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Entrees","sortIdx":27,"items":[{"item":"Roasted Eggplant & Zucchini Casserole","healthy":false,"sortIdx":1},{"item":"Chef's Choice Vegan Entree","healthy":false,"sortIdx":2},{"item":"Chef's Choice Meat Entree","healthy":false,"sortIdx":3},{"item":"Roasted Pork Loin with Mango Mojo","healthy":true,"sortIdx":4},{"item":"Gourmet Pretzel Bar","healthy":false,"sortIdx":5}]},{"category":"Hot Traditional Station - Sides","sortIdx":28,"items":[{"item":"Steamed Winter Vegetables","healthy":true,"sortIdx":1},{"item":"Sauteed Super Greens","healthy":true,"sortIdx":2},{"item":"Roasted Garlic & Herb Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":29,"items":[{"item":"Veggie Burger","healthy":true,"sortIdx":1},{"item":"Char-Grilled Hamburgers","healthy":false,"sortIdx":2},{"item":"Build Your Own Nachos","healthy":false,"sortIdx":3}]},{"category":"Pasta and Pizza Station","sortIdx":30,"items":[{"item":"Pasta By Design","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2},{"item":"Broccoli Alfredo Pizza","healthy":false,"sortIdx":3},{"item":"Buffalo Chicken Pizza","healthy":false,"sortIdx":4}]},{"category":"Wok\/Asian Station","sortIdx":31,"items":[{"item":"Pancake Bar with Fruit Toppings & Syrups","healthy":false,"sortIdx":1},{"item":"Omelet Bar","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640260800,"endTimestamp":1640273400,"start":"7:00am","end":"10:30am","menu":[],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640273400,"endTimestamp":1640287800,"start":"10:30am","end":"2:30pm","menu":[],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640296800,"endTimestamp":1640305800,"start":"5:00pm","end":"7:30pm","menu":[],"calSummary":"Dinner"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":30,"slug":"Rose-House-Dining","name":"Rose House Dining Room","nameshort":"Rose House Dining","about":"
\r\nThe Rose House Dining Room<\/strong> is a
dning room<\/a> for house residents, and for the entire Cornell community. This eatery features traditional hot entrees, an Asian station, a grill, a deli, and a salad bar. Continental breakfast on Saturday and Sunday. Sample Chef Matt Seeber's daily menu options \u2013 you won't be disappointed!\r\n

\r\nMore Info:
Rose House Dining Room<\/strong><\/a>\r\n

\r\n
\"Rose<\/a>\r\n

","aboutshort":"Dining room located in Flora Rose House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"mo4mqfpe88ucqaer728ovfei18@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-0337","contactEmail":null,"serviceUnitId":4,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447813,"longitude":-76.488791,"location":"Flora Rose House","coordinates":{"latitude":42.447813,"longitude":-76.488791},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":21,"slug":"Rustys","name":"Rusty's","nameshort":"Rusty's","about":"
\r\nConveniently located in the lobby of Uris Hall, Rusty's<\/strong> offers Starbucks specialty coffees, Straight from the Oven baked goods, Freshtake Grab-n-Go sandwiches, salads and more. At any time of day, you're sure to find something to whet your appetite!\r\n

\r\nMore Info:
Rusty's<\/strong><\/a>\r\n

\r\n
\"Rusty's<\/a>\r\n

","aboutshort":"A great place to grab a quick coffee or a bite to eat on your way to class or work.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sqp9nd9rt727fm7v2sgmfelkps@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6656","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447399,"longitude":-76.482302,"location":"Uris Hall","coordinates":{"latitude":42.447399,"longitude":-76.482302},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":13,"slug":"StraightMarket","name":"Straight from the Market","nameshort":"Straight from the Market","about":"With a farm-fresh marketplace flair, Straight from the Market offers a wide variety of fresh and marinated vegetables, hummus and tapenade bar, salad fixings, and Cornell Dairy ice cream on the main floor of Willard Straight Hall adjacent to the Straight Terrace. Healthy, flavorful, and ready-to-go meat, vegan, and vegetarian choices daily.","aboutshort":"A farm-fresh marketplace on the main floor of the Straight.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ju94n6trv0ccoqcnd5u7otle50@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3963","contactEmail":null,"serviceUnitId":11,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446372,"longitude":-76.485786,"location":"Willard Straight Hall","coordinates":{"latitude":42.446372,"longitude":-76.485786},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":23,"slug":"Trillium","name":"Trillium","nameshort":"Trillium","about":"
\r\nAt Trillium<\/strong> choose from a wide variety of food stations serving Mexican food, Asian dishes, sumptuous burgers, pasta bar, sandwiches, salads, soups, and made-to-order omelets, among many other delectable menu options.\r\n

\r\nMore Info:
Trillium<\/strong><\/a>\r\n

\r\n
\"Trillium<\/a>\r\n

","aboutshort":"Located in Kennedy Hall in the heart of Central Campus, Trillium is one of our most popular food courts.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"i8v43jd76mugc62voucp4dqn9s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447883,"longitude":-76.479127,"location":"Kennedy Hall","coordinates":{"latitude":42.447883,"longitude":-76.479127},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Food Court","descrshort":"food court"}],"diningCuisines":[{"name":"Mexican","nameshort":"Mexican","descr":""},{"name":"Asian","nameshort":"Asian","descr":""},{"name":"Pizza","nameshort":"Pizza","descr":""},{"name":"Italian","nameshort":"Italian","descr":null}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"fastfood-blue"}]},"message":null,"meta":{"copyright":"Cornell University, Cornell Dining","responseDttm":"2021-12-22T11:15:18-0500"}} \ No newline at end of file From 3c3f4ac681f445e55c9de7797b36d71de9aed405 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 22 Feb 2024 13:32:28 -0500 Subject: [PATCH 203/305] Refactoring cron logic --- src/{ => cron}/cron_log.txt | 0 src/cron/update_db.txt | 1 + 2 files changed, 1 insertion(+) rename src/{ => cron}/cron_log.txt (100%) create mode 100644 src/cron/update_db.txt diff --git a/src/cron_log.txt b/src/cron/cron_log.txt similarity index 100% rename from src/cron_log.txt rename to src/cron/cron_log.txt diff --git a/src/cron/update_db.txt b/src/cron/update_db.txt new file mode 100644 index 0000000..f33fcdb --- /dev/null +++ b/src/cron/update_db.txt @@ -0,0 +1 @@ +0 11-23 * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /usr/app/cron/cron_log.txt 2>&1; From af1d0068d02ceafd7b044a098fb7aedccddd236c Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 22 Feb 2024 13:35:16 -0500 Subject: [PATCH 204/305] cron wasnt being tracked --- Dockerfile | 2 +- docker-compose.server.yml | 2 +- docker-compose.yml | 2 +- update_db.txt | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 update_db.txt diff --git a/Dockerfile b/Dockerfile index 3eb052d..fb6c03c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,6 @@ COPY ./src . COPY ./requirements.txt . RUN pip install -r requirements.txt -COPY update_db.txt /etc/cron.d/update_db +RUN mv ./cron/update_db.txt /etc/cron.d/update_db RUN chmod 0766 manage.py RUN crontab /etc/cron.d/update_db diff --git a/docker-compose.server.yml b/docker-compose.server.yml index f5c6eed..5649e1f 100644 --- a/docker-compose.server.yml +++ b/docker-compose.server.yml @@ -6,7 +6,7 @@ services: env_file: .env stdin_open: true # docker run -i tty: true # docker run -t - command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron -f & python manage.py runserver 0.0.0.0:8000; }" + command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron start & python manage.py runserver 0.0.0.0:8000; }" ports: - "8000:8000" restart: always diff --git a/docker-compose.yml b/docker-compose.yml index 20523d1..0d7bfb6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: env_file: .env stdin_open: true # docker run -i tty: true # docker run -t - command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron -f & python manage.py runserver 0.0.0.0:80; }" + command: sh -c "python manage.py migrate && printenv > /etc/environment && { cron start & python manage.py runserver 0.0.0.0:80; }" ports: - "80:80" restart: always diff --git a/update_db.txt b/update_db.txt deleted file mode 100644 index 5892eff..0000000 --- a/update_db.txt +++ /dev/null @@ -1 +0,0 @@ -0 11-23 * * * cd /usr/app; /usr/local/bin/python /usr/app/manage.py populate_models > /usr/app/cron_log.txt 2>&1; From 030a8a809fa1beff3291af3ecea33bc197a051ee Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 22 Feb 2024 13:53:41 -0500 Subject: [PATCH 205/305] Simplifying reset and start files --- README.md | 7 +++++-- src/reset_db.sh | 5 ----- src/start.sh | 3 --- 3 files changed, 5 insertions(+), 10 deletions(-) delete mode 100644 src/reset_db.sh delete mode 100644 src/start.sh diff --git a/README.md b/README.md index c58c772..e51d8fd 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,11 @@ This is the backend for eatery-blue-backend. - Create the eatery database via `create database "eatery-dev";` - Quit psql via `\q` - Create an `.envrc` file and fill out the environment variables from the `.envrctemplate` file corresponding to your local postgres database -- To set up the tables and data, make sure current working directory is the `src` folder and run `bash reset_db.sh` -- To run the backend, run `bash start.sh` +- Create a python virtual environment in the root directory by running `python3 -m venv venv` and then activate it by running `source venv/bin/activate` (MacOS) or `venv\Scripts\activate` (Windows) +- Install the required dependencies by running `pip3 install -r requirements.txt` +- Load the environment variables by running `source .envrc` +- To set up the tables and data (or if reseting the database), make sure current working directory is the `src` folder and run `python3 manage.py makemigrations; python3 manage.py migrate; python3 manage.py populate_models` +- To run the backend, run `python3 manage.py runserver 0.0.0.0:8000` (Ensuring the env variables are loaded and all dependencies are installed) ## SP24 Members diff --git a/src/reset_db.sh b/src/reset_db.sh deleted file mode 100644 index 4519442..0000000 --- a/src/reset_db.sh +++ /dev/null @@ -1,5 +0,0 @@ -source ../venv/bin/activate -source ../.envrc -python3 manage.py makemigrations -python3 manage.py migrate -python3 manage.py populate_models diff --git a/src/start.sh b/src/start.sh deleted file mode 100644 index d0b55a7..0000000 --- a/src/start.sh +++ /dev/null @@ -1,3 +0,0 @@ -source ../venv/bin/activate -source ../.envrc -python3 manage.py runserver 0.0.0.0:8000 \ No newline at end of file From 560ef0e1a7ac5a222818db4f935f60e707b2130f Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Wed, 28 Feb 2024 16:44:18 -0500 Subject: [PATCH 206/305] Adding crud endpoints for reports --- src/report/urls.py | 21 +++++++++++++----- src/report/views.py | 54 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/report/urls.py b/src/report/urls.py index 2581e70..8805b0e 100644 --- a/src/report/urls.py +++ b/src/report/urls.py @@ -1,10 +1,19 @@ -from django.urls import path, include -from rest_framework.routers import DefaultRouter -from report import views +from django.urls import path +from report.views import ReportViewSet -router = DefaultRouter() -router.register(r'', views.ReportViewSet) +reports_list = ReportViewSet.as_view({ + 'get': 'list', + 'post': 'create' +}) + +report_list = ReportViewSet.as_view({ + 'get':'retrieve', + 'put':'update', + 'patch':'partial_update', + 'delete':'destroy' +}) urlpatterns = [ - path('', include(router.urls)) + path("", reports_list, name='report-list'), + path("/", report_list, name='report-list'), ] \ No newline at end of file diff --git a/src/report/views.py b/src/report/views.py index b638a40..f076e67 100644 --- a/src/report/views.py +++ b/src/report/views.py @@ -4,19 +4,53 @@ from .permissions import ReportPermission from rest_framework.decorators import action from rest_framework.response import Response +from django.shortcuts import get_object_or_404 import os class ReportViewSet(viewsets.ModelViewSet): queryset = Report.objects.all() serializer_class = ReportSerializer - permission_classes = [ReportPermission] + # permission_classes = [ReportPermission] + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + serializer = ReportSerializer(instance) + return Response(serializer.data) + + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + serializer = ReportSerializer(queryset, many=True) + return Response(serializer.data) + + def get_object(self): + queryset = self.filter_queryset(self.get_queryset()) + lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - @action(url_path='custom_retrieve',detail=False, methods=['GET']) - def custom_retrieve(self, request, pk=None): - if 'key' not in request.headers: - return Response({"error": "no key"}, status=401) - if request.headers['key'] != os.environ.get('REPORT_KEY'): - return Response({"error": "invalid key"}, status=401) - report = Report.objects.all() - serializer = ReportSerializer(report, many=True) - return Response(serializer.data, status=200) \ No newline at end of file + assert lookup_url_kwarg in self.kwargs, ( + 'Expected view %s to be called with a URL keyword argument ' + 'named "%s". Fix your URL conf, or set the `.lookup_field` ' + 'attribute on the view correctly.' % + (self.__class__.__name__, lookup_url_kwarg) + ) + # Uses the lookup_field attribute, which defaults to `pk` + filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} + obj = get_object_or_404(queryset, **filter_kwargs) + + # May raise a permission denied + self.check_object_permissions(self.request, obj) + return obj + + # TODO + def update(self, request, *args, **kwargs): + # Add request validation - see verify_json_fields in eatery/util/json.py + pass + + # @action(url_path='custom_retrieve',detail=False, methods=['GET']) + # def custom_retrieve(self, request, pk=None): + # if 'key' not in request.headers: + # return Response({"error": "no key"}, status=401) + # if request.headers['key'] != os.environ.get('REPORT_KEY'): + # return Response({"error": "invalid key"}, status=401) + # report = Report.objects.all() + # serializer = ReportSerializer(report, many=True) + # return Response(serializer.data, status=200) \ No newline at end of file From 6aca23a3706b2e743ecc9545f85291f0cef225d5 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 29 Feb 2024 12:16:31 -0500 Subject: [PATCH 207/305] Fixing viewset --- .gitignore | 2 +- src/eatery_blue_backend/settings.py | 6 ++- src/eatery_blue_backend/urls.py | 15 ------- src/report/controllers/update_report.py | 32 +++++++++++++++ src/report/migrations/0002_report_netid.py | 18 ++++++++ src/report/models.py | 10 +++-- src/report/serializers.py | 3 +- src/report/urls.py | 19 +++------ src/report/views.py | 48 +--------------------- 9 files changed, 69 insertions(+), 84 deletions(-) create mode 100644 src/report/controllers/update_report.py create mode 100644 src/report/migrations/0002_report_netid.py diff --git a/.gitignore b/.gitignore index 419915a..749e7f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Security .env -eatery-dev.pem +*.pem .envrc .envlocal .envremote diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 685cf6f..53dbae8 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -40,7 +40,8 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework.authtoken", - "rest_framework", + + # Apps "eatery_blue_backend", "eatery", "event", @@ -48,6 +49,9 @@ "item", "category", "person", + + # Third party + "rest_framework", ] MIDDLEWARE = [ diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index e943338..6994824 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -1,18 +1,3 @@ -"""eatery_blue_backend URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/4.0/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" from django.contrib import admin from django.urls import include, path diff --git a/src/report/controllers/update_report.py b/src/report/controllers/update_report.py new file mode 100644 index 0000000..27dd8d7 --- /dev/null +++ b/src/report/controllers/update_report.py @@ -0,0 +1,32 @@ +from django.http import QueryDict + +from eatery.datatype.Eatery import EateryID +from report.models import Report + + +class UpdateReportController: + def __init__(self, id: int, update_map: QueryDict): + """ + Update_map is a dictionary that maps the fields we want to update to + the values we want to map them to + + Requires: id is a valid id and all keys in update_map are valid fields + in the EateryStore class (except username/password cannot be provided), + as well as an optional image field containing an image file to be uploaded + """ + self.id = id + self.update_data = {} + + # Query dict is immutable, so need to do this to remove id + to_remove = ["id"] + self.update_data = {} + for key, val in update_map.items(): + if key not in to_remove: + self.update_data[key] = val + + def process(self): + """ + Selects DB entry we want to update and updates it using provided data + """ + Report.objects.filter(id=self.id.value).update(**self.update_data) + \ No newline at end of file diff --git a/src/report/migrations/0002_report_netid.py b/src/report/migrations/0002_report_netid.py new file mode 100644 index 0000000..22c3b63 --- /dev/null +++ b/src/report/migrations/0002_report_netid.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-02-29 04:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('report', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='report', + name='netid', + field=models.CharField(blank=True, max_length=10, null=True), + ), + ] diff --git a/src/report/models.py b/src/report/models.py index 786d0ef..aea9fb7 100644 --- a/src/report/models.py +++ b/src/report/models.py @@ -1,11 +1,13 @@ from django.db import models from eatery.models import Eatery - class Report(models.Model): eatery = models.ForeignKey(Eatery, on_delete=models.CASCADE, null=True, blank=True) + netid = models.CharField(max_length=10, null=True, blank=True) content = models.TextField() created = models.DateTimeField(auto_now_add=True) - -def __str__(self): - return self.content \ No newline at end of file + + def __str__(self): + return f'{self.content} - {self.created}' + + diff --git a/src/report/serializers.py b/src/report/serializers.py index 0206d8e..e5473bb 100644 --- a/src/report/serializers.py +++ b/src/report/serializers.py @@ -1,8 +1,7 @@ from rest_framework import serializers - from report.models import Report class ReportSerializer(serializers.ModelSerializer): class Meta: model = Report - fields = ['id', 'eatery', 'content', 'created'] + fields = ['id', 'eatery', 'netid', 'content', 'created'] diff --git a/src/report/urls.py b/src/report/urls.py index 8805b0e..4e2d6ad 100644 --- a/src/report/urls.py +++ b/src/report/urls.py @@ -1,19 +1,10 @@ -from django.urls import path +from django.urls import path, include from report.views import ReportViewSet +from rest_framework.routers import DefaultRouter -reports_list = ReportViewSet.as_view({ - 'get': 'list', - 'post': 'create' -}) - -report_list = ReportViewSet.as_view({ - 'get':'retrieve', - 'put':'update', - 'patch':'partial_update', - 'delete':'destroy' -}) +router = DefaultRouter() +router.register("", ReportViewSet) urlpatterns = [ - path("", reports_list, name='report-list'), - path("/", report_list, name='report-list'), + path("", include(router.urls)), ] \ No newline at end of file diff --git a/src/report/views.py b/src/report/views.py index f076e67..e728fbd 100644 --- a/src/report/views.py +++ b/src/report/views.py @@ -2,55 +2,9 @@ from report.models import Report from report.serializers import ReportSerializer from .permissions import ReportPermission -from rest_framework.decorators import action -from rest_framework.response import Response -from django.shortcuts import get_object_or_404 -import os class ReportViewSet(viewsets.ModelViewSet): queryset = Report.objects.all() serializer_class = ReportSerializer # permission_classes = [ReportPermission] - - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = ReportSerializer(instance) - return Response(serializer.data) - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - serializer = ReportSerializer(queryset, many=True) - return Response(serializer.data) - - def get_object(self): - queryset = self.filter_queryset(self.get_queryset()) - lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - - assert lookup_url_kwarg in self.kwargs, ( - 'Expected view %s to be called with a URL keyword argument ' - 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - 'attribute on the view correctly.' % - (self.__class__.__name__, lookup_url_kwarg) - ) - # Uses the lookup_field attribute, which defaults to `pk` - filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} - obj = get_object_or_404(queryset, **filter_kwargs) - - # May raise a permission denied - self.check_object_permissions(self.request, obj) - return obj - - # TODO - def update(self, request, *args, **kwargs): - # Add request validation - see verify_json_fields in eatery/util/json.py - pass - - # @action(url_path='custom_retrieve',detail=False, methods=['GET']) - # def custom_retrieve(self, request, pk=None): - # if 'key' not in request.headers: - # return Response({"error": "no key"}, status=401) - # if request.headers['key'] != os.environ.get('REPORT_KEY'): - # return Response({"error": "invalid key"}, status=401) - # report = Report.objects.all() - # serializer = ReportSerializer(report, many=True) - # return Response(serializer.data, status=200) \ No newline at end of file + \ No newline at end of file From ca8863dcbeb1341f62aec095c0c3b2c5eab6951d Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 29 Feb 2024 15:08:07 -0500 Subject: [PATCH 208/305] Finishing reports and changing PK of other models --- README.md | 1 + .../migrations/0002_alter_category_id.py | 18 +++++++++++ src/category/models.py | 1 - src/event/apps.py | 1 + src/event/migrations/0002_alter_event_id.py | 18 +++++++++++ src/event/models.py | 1 - src/item/migrations/0002_alter_item_id.py | 18 +++++++++++ src/item/models.py | 1 - src/report/controllers/update_report.py | 32 ------------------- src/report/models.py | 1 + src/report/permissions.py | 11 ------- src/report/serializers.py | 1 + src/report/urls.py | 1 + src/report/views.py | 4 +-- 14 files changed, 60 insertions(+), 49 deletions(-) create mode 100644 src/category/migrations/0002_alter_category_id.py create mode 100644 src/event/migrations/0002_alter_event_id.py create mode 100644 src/item/migrations/0002_alter_item_id.py delete mode 100644 src/report/controllers/update_report.py delete mode 100644 src/report/permissions.py diff --git a/README.md b/README.md index e51d8fd..0f65339 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ This is the backend for eatery-blue-backend. - Thomas Vignos - Aayush Agnihotri +- Daniel Weiner ## FA23 Members diff --git a/src/category/migrations/0002_alter_category_id.py b/src/category/migrations/0002_alter_category_id.py new file mode 100644 index 0000000..410927c --- /dev/null +++ b/src/category/migrations/0002_alter_category_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-02-29 17:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('category', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='category', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/src/category/models.py b/src/category/models.py index 715a96e..692bf28 100644 --- a/src/category/models.py +++ b/src/category/models.py @@ -4,6 +4,5 @@ class Category(models.Model): - id = models.AutoField(primary_key=True) event = models.ForeignKey(Event, related_name="menu", on_delete=models.DO_NOTHING) category = models.CharField(max_length=40, default="General") diff --git a/src/event/apps.py b/src/event/apps.py index a52809c..766f251 100644 --- a/src/event/apps.py +++ b/src/event/apps.py @@ -1,5 +1,6 @@ from django.apps import AppConfig class EventConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' name = "event" diff --git a/src/event/migrations/0002_alter_event_id.py b/src/event/migrations/0002_alter_event_id.py new file mode 100644 index 0000000..6570118 --- /dev/null +++ b/src/event/migrations/0002_alter_event_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-02-29 17:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/src/event/models.py b/src/event/models.py index 539f1ee..3121e49 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -13,7 +13,6 @@ class EventDescription(models.TextChoices): PANTS = "Pants" class Event(models.Model): - id = models.AutoField(primary_key=True) eatery = models.ForeignKey(Eatery, related_name = "events", on_delete=models.DO_NOTHING) event_description = models.TextField( choices=EventDescription.choices, default = EventDescription.GENERAL, blank=True, null = True) diff --git a/src/item/migrations/0002_alter_item_id.py b/src/item/migrations/0002_alter_item_id.py new file mode 100644 index 0000000..7d59a20 --- /dev/null +++ b/src/item/migrations/0002_alter_item_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-02-29 17:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('item', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='item', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/src/item/models.py b/src/item/models.py index a2b3fa4..9a8a900 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -3,7 +3,6 @@ from category.models import Category class Item(models.Model): - id = models.AutoField(primary_key=True) category = models.ForeignKey(Category, related_name = "items", on_delete=models.DO_NOTHING) name = models.CharField(max_length=40, default = "Item") base_price = models.FloatField(null=True, blank=True, default=0.0) diff --git a/src/report/controllers/update_report.py b/src/report/controllers/update_report.py deleted file mode 100644 index 27dd8d7..0000000 --- a/src/report/controllers/update_report.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.http import QueryDict - -from eatery.datatype.Eatery import EateryID -from report.models import Report - - -class UpdateReportController: - def __init__(self, id: int, update_map: QueryDict): - """ - Update_map is a dictionary that maps the fields we want to update to - the values we want to map them to - - Requires: id is a valid id and all keys in update_map are valid fields - in the EateryStore class (except username/password cannot be provided), - as well as an optional image field containing an image file to be uploaded - """ - self.id = id - self.update_data = {} - - # Query dict is immutable, so need to do this to remove id - to_remove = ["id"] - self.update_data = {} - for key, val in update_map.items(): - if key not in to_remove: - self.update_data[key] = val - - def process(self): - """ - Selects DB entry we want to update and updates it using provided data - """ - Report.objects.filter(id=self.id.value).update(**self.update_data) - \ No newline at end of file diff --git a/src/report/models.py b/src/report/models.py index aea9fb7..6b81978 100644 --- a/src/report/models.py +++ b/src/report/models.py @@ -1,6 +1,7 @@ from django.db import models from eatery.models import Eatery + class Report(models.Model): eatery = models.ForeignKey(Eatery, on_delete=models.CASCADE, null=True, blank=True) netid = models.CharField(max_length=10, null=True, blank=True) diff --git a/src/report/permissions.py b/src/report/permissions.py deleted file mode 100644 index 47a59f3..0000000 --- a/src/report/permissions.py +++ /dev/null @@ -1,11 +0,0 @@ -from rest_framework import permissions - -class ReportPermission(permissions.BasePermission): - - def has_permission(self, request, view): - if view.action in ['create', 'custom_retrieve']: - return True - return request.user.is_staff - - def has_object_permission(self, request, view, obj): - return request.user.is_staff \ No newline at end of file diff --git a/src/report/serializers.py b/src/report/serializers.py index e5473bb..1356886 100644 --- a/src/report/serializers.py +++ b/src/report/serializers.py @@ -1,6 +1,7 @@ from rest_framework import serializers from report.models import Report + class ReportSerializer(serializers.ModelSerializer): class Meta: model = Report diff --git a/src/report/urls.py b/src/report/urls.py index 4e2d6ad..e4a4df6 100644 --- a/src/report/urls.py +++ b/src/report/urls.py @@ -2,6 +2,7 @@ from report.views import ReportViewSet from rest_framework.routers import DefaultRouter + router = DefaultRouter() router.register("", ReportViewSet) diff --git a/src/report/views.py b/src/report/views.py index e728fbd..f079a26 100644 --- a/src/report/views.py +++ b/src/report/views.py @@ -1,10 +1,8 @@ from rest_framework import viewsets from report.models import Report from report.serializers import ReportSerializer -from .permissions import ReportPermission + class ReportViewSet(viewsets.ModelViewSet): queryset = Report.objects.all() serializer_class = ReportSerializer - # permission_classes = [ReportPermission] - \ No newline at end of file From e16e5ff3f383a073add7245b2296539e6c6982c5 Mon Sep 17 00:00:00 2001 From: Daniel Weiner Date: Sat, 2 Mar 2024 16:29:01 -0500 Subject: [PATCH 209/305] Events refactor --- src/event/urls.py | 10 ++++++++++ src/event/views.py | 8 ++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 src/event/urls.py diff --git a/src/event/urls.py b/src/event/urls.py new file mode 100644 index 0000000..5c0709c --- /dev/null +++ b/src/event/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from event.views import EventViewSet +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register("", EventViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] \ No newline at end of file diff --git a/src/event/views.py b/src/event/views.py index 91ea44a..a0bdf91 100644 --- a/src/event/views.py +++ b/src/event/views.py @@ -1,3 +1,7 @@ -from django.shortcuts import render +from rest_framework import viewsets +from event.models import Event +from event.serializers import EventSerializer -# Create your views here. +class EventViewSet(viewsets.ModelViewSet): + queryset = Event.objects.all() + serializer_class = EventSerializer \ No newline at end of file From 655d4a62e8f0bbfbb45afa12b69272d8ffcdd0f7 Mon Sep 17 00:00:00 2001 From: Daniel Weiner Date: Sun, 3 Mar 2024 12:44:07 -0500 Subject: [PATCH 210/305] Category and item crud endpoints --- src/category/urls.py | 10 ++++++++++ src/category/views.py | 8 ++++++-- src/item/urls.py | 10 ++++++++++ src/item/views.py | 8 ++++++-- 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/category/urls.py create mode 100644 src/item/urls.py diff --git a/src/category/urls.py b/src/category/urls.py new file mode 100644 index 0000000..d32462d --- /dev/null +++ b/src/category/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from category.views import CategoryViewSet +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register("", CategoryViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] \ No newline at end of file diff --git a/src/category/views.py b/src/category/views.py index 91ea44a..a95917c 100644 --- a/src/category/views.py +++ b/src/category/views.py @@ -1,3 +1,7 @@ -from django.shortcuts import render +from rest_framework import viewsets +from category.models import Category +from category.serializers import CategorySerializer -# Create your views here. +class CategoryViewSet(viewsets.ModelViewSet): + queryset = Category.objects.all() + serializer_class = CategorySerializer \ No newline at end of file diff --git a/src/item/urls.py b/src/item/urls.py new file mode 100644 index 0000000..6c5de78 --- /dev/null +++ b/src/item/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from item.views import ItemViewSet +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register("", ItemViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] \ No newline at end of file diff --git a/src/item/views.py b/src/item/views.py index 91ea44a..745b028 100644 --- a/src/item/views.py +++ b/src/item/views.py @@ -1,3 +1,7 @@ -from django.shortcuts import render +from rest_framework import viewsets +from item.models import Item +from item.serializers import ItemSerializer -# Create your views here. +class ItemViewSet(viewsets.ModelViewSet): + queryset = Item.objects.all() + serializer_class = ItemSerializer \ No newline at end of file From 0389cdc2b3a88f5ae206ecdde985812f90949f6c Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Sun, 3 Mar 2024 14:58:06 -0500 Subject: [PATCH 211/305] miscellaneous --- .envrctemplate | 11 +++++------ src/category/models.py | 1 - src/category/tests.py | 3 --- src/eatery/admin.py | 4 ++-- src/eatery/migrations/0002_alter_eatery_id.py | 18 ++++++++++++++++++ src/eatery/models.py | 1 - src/eatery_blue_backend/urls.py | 3 +++ src/person/permissions.py | 17 ----------------- src/person/tests.py | 3 --- src/person/urls.py | 8 ++++---- src/person/views.py | 5 +---- 11 files changed, 33 insertions(+), 41 deletions(-) delete mode 100644 src/category/tests.py create mode 100644 src/eatery/migrations/0002_alter_eatery_id.py delete mode 100644 src/person/permissions.py delete mode 100644 src/person/tests.py diff --git a/.envrctemplate b/.envrctemplate index 4b6fd4e..f5d34f9 100644 --- a/.envrctemplate +++ b/.envrctemplate @@ -1,5 +1,3 @@ -export CORNELL_VENDOR_TOKEN= -export CORNELL_VENDOR_API_KEY= export DJANGO_ALLOWED_HOSTS= export IS_PROD= export POSTGRES_NAME= @@ -7,7 +5,8 @@ export POSTGRES_USER= export POSTGRES_PASSWORD= export POSTGRES_HOST= export POSTGRES_PORT= -export VENDOR_API_KEY= -export VENDOR_BEARER_TOKEN= -export REPORT_KEY= -export DJANGO_SECRET_KEY= \ No newline at end of file +export DJANGO_SECRET_KEY= + +# Not used +export CORNELL_VENDOR_TOKEN= +export CORNELL_VENDOR_API_KEY= \ No newline at end of file diff --git a/src/category/models.py b/src/category/models.py index 692bf28..5a5bbff 100644 --- a/src/category/models.py +++ b/src/category/models.py @@ -1,5 +1,4 @@ from django.db import models -from eatery.models import Eatery from event.models import Event diff --git a/src/category/tests.py b/src/category/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/category/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/eatery/admin.py b/src/eatery/admin.py index 3884336..245be1e 100644 --- a/src/eatery/admin.py +++ b/src/eatery/admin.py @@ -1,4 +1,4 @@ from django.contrib import admin -import eatery.models as models +from eatery.models import Eatery -admin.site.register(models.Eatery) +admin.site.register(Eatery) diff --git a/src/eatery/migrations/0002_alter_eatery_id.py b/src/eatery/migrations/0002_alter_eatery_id.py new file mode 100644 index 0000000..714af0b --- /dev/null +++ b/src/eatery/migrations/0002_alter_eatery_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-02-29 21:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='eatery', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/src/eatery/models.py b/src/eatery/models.py index 48f2eb8..67752a2 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -10,7 +10,6 @@ class CampusArea(models.TextChoices): COLLEGETOWN = "Collegetown" NONE = "" - id = models.IntegerField(primary_key=True) name = models.CharField(max_length=40) menu_summary = models.TextField(blank=True, null=True, default="") image_url = models.URLField(blank=True) diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 6994824..04edb88 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -4,6 +4,9 @@ urlpatterns = [ path("admin/", admin.site.urls), path("eatery/", include("eatery.urls")), + path("event/", include("event.urls")), + # path("item/", include("item.urls")), + path("category/", include("category.urls")), path("report/", include("report.urls")), path("person/", include("person.urls")), ] diff --git a/src/person/permissions.py b/src/person/permissions.py deleted file mode 100644 index 4b9a4a8..0000000 --- a/src/person/permissions.py +++ /dev/null @@ -1,17 +0,0 @@ -from rest_framework import permissions - -class StudentPermission(permissions.BasePermission): - - def has_permission(self, request, view): - return False - - def has_object_permission(self, request, view, obj): - return False - -class ChefPermission(permissions.BasePermission): - - def has_permission(self, request, view): - return False - - def has_object_permission(self, request, view, obj): - return False \ No newline at end of file diff --git a/src/person/tests.py b/src/person/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/person/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/person/urls.py b/src/person/urls.py index 9c7b45d..75e30dc 100644 --- a/src/person/urls.py +++ b/src/person/urls.py @@ -1,11 +1,11 @@ from django.urls import path, include +from person.views import StudentViewSet, ChefViewSet from rest_framework.routers import DefaultRouter -from person import views router = DefaultRouter() -router.register(r'student', views.StudentViewSet) -router.register(r'chef', views.ChefViewSet) +router.register("student", StudentViewSet) +router.register("chef", ChefViewSet) urlpatterns = [ - path('', include(router.urls)), + path("", include(router.urls)), ] \ No newline at end of file diff --git a/src/person/views.py b/src/person/views.py index 2395fc3..778d6e7 100644 --- a/src/person/views.py +++ b/src/person/views.py @@ -1,14 +1,11 @@ from rest_framework import viewsets from person.models import Student, Chef from person.serializers import StudentSerializer, ChefSerializer -from .permissions import StudentPermission, ChefPermission class StudentViewSet(viewsets.ModelViewSet): queryset = Student.objects.all() serializer_class = StudentSerializer - permission_classes = [StudentPermission] class ChefViewSet(viewsets.ModelViewSet): queryset = Chef.objects.all() - serializer_class = ChefSerializer - permission_classes = [ChefPermission] \ No newline at end of file + serializer_class = ChefSerializer \ No newline at end of file From f0f57ad623ca1bb761a53d0888a8b8ff805e5f80 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Sun, 3 Mar 2024 17:02:31 -0500 Subject: [PATCH 212/305] Adding str function to models --- src/category/models.py | 3 +++ src/eatery/models.py | 3 +++ src/eatery/tests.py | 3 --- src/eatery_blue_backend/urls.py | 2 +- src/event/models.py | 3 +++ src/item/models.py | 3 +++ src/item/tests.py | 3 --- src/person/models.py | 8 +++++++- 8 files changed, 20 insertions(+), 8 deletions(-) delete mode 100644 src/eatery/tests.py delete mode 100644 src/item/tests.py diff --git a/src/category/models.py b/src/category/models.py index 5a5bbff..01b85aa 100644 --- a/src/category/models.py +++ b/src/category/models.py @@ -5,3 +5,6 @@ class Category(models.Model): event = models.ForeignKey(Event, related_name="menu", on_delete=models.DO_NOTHING) category = models.CharField(max_length=40, default="General") + + def __str__(self): + return self.category diff --git a/src/eatery/models.py b/src/eatery/models.py index 67752a2..64cf5a9 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -23,3 +23,6 @@ class CampusArea(models.TextChoices): payment_accepts_meal_swipes = models.BooleanField(null=True, blank=True) payment_accepts_brbs = models.BooleanField(null=True, blank=True) payment_accepts_cash = models.BooleanField(null=True, blank=True) + + def __str__(self): + return self.name diff --git a/src/eatery/tests.py b/src/eatery/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/eatery/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 04edb88..f289980 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -5,7 +5,7 @@ path("admin/", admin.site.urls), path("eatery/", include("eatery.urls")), path("event/", include("event.urls")), - # path("item/", include("item.urls")), + path("item/", include("item.urls")), path("category/", include("category.urls")), path("report/", include("report.urls")), path("person/", include("person.urls")), diff --git a/src/event/models.py b/src/event/models.py index 3121e49..d71a303 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -18,6 +18,9 @@ class Event(models.Model): choices=EventDescription.choices, default = EventDescription.GENERAL, blank=True, null = True) start = models.IntegerField(default = 0) end = models.IntegerField(default = 0) + + def __str__(self): + return f"{self.eatery.name}: {self.event_description} from {self.start} to {self.end}" @classmethod def truncate(cls): diff --git a/src/item/models.py b/src/item/models.py index 9a8a900..223539d 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -6,3 +6,6 @@ class Item(models.Model): category = models.ForeignKey(Category, related_name = "items", on_delete=models.DO_NOTHING) name = models.CharField(max_length=40, default = "Item") base_price = models.FloatField(null=True, blank=True, default=0.0) + + def __str__(self): + return f"{self.name} ({self.category.name})" diff --git a/src/item/tests.py b/src/item/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/src/item/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/src/person/models.py b/src/person/models.py index 9e121f5..82067aa 100644 --- a/src/person/models.py +++ b/src/person/models.py @@ -7,7 +7,13 @@ class Student(models.Model): favorite_eateries = models.ManyToManyField('eatery.Eatery', related_name='student') favorite_items = models.ManyToManyField('item.Item', related_name='student') user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) + + def __str__(self): + return f"Student {self.net_id}" class Chef(models.Model): eateries_managed = models.ManyToManyField('eatery.Eatery', related_name='chef') - user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) \ No newline at end of file + user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) + + def __str__(self): + return f"Chef {self.user.username}" \ No newline at end of file From bacf67f6464000e796a3ccad172cddfb18f3212a Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Sun, 10 Mar 2024 11:34:52 -0400 Subject: [PATCH 213/305] Remove redundant ids --- src/category/serializers.py | 6 +++--- .../migrations/0003_alter_eatery_image_url.py | 18 ++++++++++++++++++ .../0004_alter_eatery_campus_area.py | 18 ++++++++++++++++++ .../0005_alter_eatery_campus_area.py | 18 ++++++++++++++++++ src/eatery/models.py | 2 +- src/eatery/serializers.py | 9 +++++---- src/eatery/views.py | 6 +++--- .../0003_alter_event_event_description.py | 18 ++++++++++++++++++ .../0004_alter_event_event_description.py | 18 ++++++++++++++++++ src/event/serializers.py | 7 +++---- src/item/serializers.py | 2 +- 11 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 src/eatery/migrations/0003_alter_eatery_image_url.py create mode 100644 src/eatery/migrations/0004_alter_eatery_campus_area.py create mode 100644 src/eatery/migrations/0005_alter_eatery_campus_area.py create mode 100644 src/event/migrations/0003_alter_event_event_description.py create mode 100644 src/event/migrations/0004_alter_event_event_description.py diff --git a/src/category/serializers.py b/src/category/serializers.py index 7fc052f..56ee284 100644 --- a/src/category/serializers.py +++ b/src/category/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from category.models import Category -from item.serializers import ItemSerializer, ItemReadSerializer +from item.serializers import ItemSerializer, ItemSerializerOptimized class CategorySerializer(serializers.ModelSerializer): @@ -16,8 +16,8 @@ class Meta: model = Category fields = ["id", "category", "event", "items"] -class CategoryReadSerializer(serializers.ModelSerializer): - items = ItemReadSerializer(many=True, read_only=True) +class CategorySerializerOptimized(serializers.ModelSerializer): + items = ItemSerializerOptimized(many=True, read_only=True) class Meta: model = Category diff --git a/src/eatery/migrations/0003_alter_eatery_image_url.py b/src/eatery/migrations/0003_alter_eatery_image_url.py new file mode 100644 index 0000000..f5e1c81 --- /dev/null +++ b/src/eatery/migrations/0003_alter_eatery_image_url.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-03-06 23:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0002_alter_eatery_id'), + ] + + operations = [ + migrations.AlterField( + model_name='eatery', + name='image_url', + field=models.URLField(blank=True, default='https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg'), + ), + ] diff --git a/src/eatery/migrations/0004_alter_eatery_campus_area.py b/src/eatery/migrations/0004_alter_eatery_campus_area.py new file mode 100644 index 0000000..c83d70d --- /dev/null +++ b/src/eatery/migrations/0004_alter_eatery_campus_area.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-03-10 14:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0003_alter_eatery_image_url'), + ] + + operations = [ + migrations.AlterField( + model_name='eatery', + name='campus_area', + field=models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('East', 'East'), ('', 'None')], default='', max_length=15), + ), + ] diff --git a/src/eatery/migrations/0005_alter_eatery_campus_area.py b/src/eatery/migrations/0005_alter_eatery_campus_area.py new file mode 100644 index 0000000..55187b0 --- /dev/null +++ b/src/eatery/migrations/0005_alter_eatery_campus_area.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-03-10 15:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0004_alter_eatery_campus_area'), + ] + + operations = [ + migrations.AlterField( + model_name='eatery', + name='campus_area', + field=models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15), + ), + ] diff --git a/src/eatery/models.py b/src/eatery/models.py index 64cf5a9..7a65c08 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -12,7 +12,7 @@ class CampusArea(models.TextChoices): name = models.CharField(max_length=40) menu_summary = models.TextField(blank=True, null=True, default="") - image_url = models.URLField(blank=True) + image_url = models.URLField(blank=True, default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") location = models.TextField(blank=True) campus_area = models.CharField( max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index bf56ef1..39f674f 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -1,7 +1,7 @@ from rest_framework import serializers from eatery.models import Eatery from event.models import Event -from event.serializers import EventSerializer, EventSerializerSimple, EventReadSerializer +from event.serializers import EventSerializer, EventSerializerSimple, EventSerializerOptimized from django.utils import timezone from datetime import date, timedelta from time import mktime @@ -30,7 +30,7 @@ class Meta: model = Eatery fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] -class EateryReadSerializer(serializers.ModelSerializer): +class EaterySerializerOptimized(serializers.ModelSerializer): id = serializers.IntegerField() name = serializers.CharField() menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") @@ -43,7 +43,8 @@ class EateryReadSerializer(serializers.ModelSerializer): payment_accepts_meal_swipes = serializers.BooleanField(allow_null=True) payment_accepts_brbs = serializers.BooleanField(allow_null=True) payment_accepts_cash = serializers.BooleanField(allow_null=True) - events = EventReadSerializer(many=True, read_only=True) + + events = EventSerializerOptimized(many=True, read_only=True) class Meta: model = Eatery @@ -69,7 +70,7 @@ def get_events(self, obj): today = (timezone.now() - timedelta(hours=5)).date() + timedelta(days=day) today_unix = mktime(today.timetuple()) + timedelta(hours=5).seconds events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=today_unix + timedelta(days=1).total_seconds()) - serializer = EventReadSerializer(instance=events, many=True) + serializer = EventSerializerOptimized(instance=events, many=True) return serializer.data class Meta: diff --git a/src/eatery/views.py b/src/eatery/views.py index d775f75..db04c35 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -1,4 +1,4 @@ -from eatery.serializers import EaterySerializer, EateryReadSerializer, EaterySerializerSimple, EaterySerializerByDay +from eatery.serializers import EaterySerializer, EaterySerializerSimple, EaterySerializerByDay, EaterySerializerOptimized from eatery.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse from django.shortcuts import get_object_or_404 @@ -22,13 +22,13 @@ class EateryViewSet(viewsets.ModelViewSet): def retrieve(self, request, *args, **kwargs): instance = self.get_object() - serializer = EaterySerializer(instance) + serializer = EaterySerializerOptimized(instance) return Response(serializer.data) @method_decorator(cache_page(60*60*2)) # cache for 2 hours def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) - serializer = EaterySerializer(queryset, many=True) + serializer = EaterySerializerOptimized(queryset, many=True) return Response(serializer.data) def get_object(self): diff --git a/src/event/migrations/0003_alter_event_event_description.py b/src/event/migrations/0003_alter_event_event_description.py new file mode 100644 index 0000000..86afd1b --- /dev/null +++ b/src/event/migrations/0003_alter_event_event_description.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-03-10 14:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0002_alter_event_id'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='event_description', + field=models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Open', 'Cafe'), ('Pants', 'Pants')], default='General', null=True), + ), + ] diff --git a/src/event/migrations/0004_alter_event_event_description.py b/src/event/migrations/0004_alter_event_event_description.py new file mode 100644 index 0000000..b3f7d98 --- /dev/null +++ b/src/event/migrations/0004_alter_event_event_description.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2024-03-10 15:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0003_alter_event_event_description'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='event_description', + field=models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', null=True), + ), + ] diff --git a/src/event/serializers.py b/src/event/serializers.py index 12dbd09..c195401 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from event.models import Event -from category.serializers import CategorySerializer, CategoryReadSerializer +from category.serializers import CategorySerializer, CategorySerializerOptimized from datetime import datetime class EventSerializer(serializers.ModelSerializer): @@ -10,7 +10,6 @@ class EventSerializer(serializers.ModelSerializer): ) start = serializers.IntegerField() end = serializers.IntegerField() - menu = CategorySerializer(many=True, read_only=True) def create(self, validated_data): @@ -21,12 +20,12 @@ class Meta: model = Event fields = ["id", "eatery", "event_description", "start", "end", "menu"] -class EventReadSerializer(serializers.ModelSerializer): +class EventSerializerOptimized(serializers.ModelSerializer): id = serializers.IntegerField(required=False, read_only=True) event_description = serializers.CharField(allow_null=True, allow_blank=True, default=None) start = serializers.IntegerField() end = serializers.IntegerField() - menu = CategoryReadSerializer(many=True, read_only=True) + menu = CategorySerializerOptimized(many=True, read_only=True) class Meta: model = Event diff --git a/src/item/serializers.py b/src/item/serializers.py index 658e779..9abc7e0 100644 --- a/src/item/serializers.py +++ b/src/item/serializers.py @@ -13,7 +13,7 @@ class Meta: model = Item fields = ['id', 'category', 'name'] -class ItemReadSerializer(serializers.ModelSerializer): +class ItemSerializerOptimized(serializers.ModelSerializer): name = serializers.CharField(default = "Item") class Meta: From fbcb24b3a9aeec9bb2ebcbb4059a035b08599984 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 13 Mar 2024 14:07:55 -0400 Subject: [PATCH 214/305] Reorder menu items --- src/category/controllers/populate_category.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index f585c05..7ec340a 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -18,7 +18,15 @@ def generate_dining_hall_categories(self, json_event, event): category_items = {"category_name" : id, ... } """ category_items = {} - for json_menu in json_event["menu"]: + + category_order = ["Chef's Table", "Chef's Table - Sides", "Grill", "Wok", + "Wok/Asian Station", "Iron Grill", "Mexican Station", "Global", + "Halal", "Kosher Station", "Flat Top Grill"] + + def sort_menu(menu): + return category_order.index(menu["category"].strip()) if menu["category"].strip() in category_order else 1000 + + for json_menu in sorted(json_event["menu"], key=sort_menu): data = {"event": event, "category": json_menu["category"]} category = CategorySerializer(data=data) From 51e7c5f7ca25ef4a7f266fedbaefe20dee1d32f6 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 13 Mar 2024 14:08:57 -0400 Subject: [PATCH 215/305] Reorder menu items --- src/category/controllers/populate_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index 7ec340a..b28ecce 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -24,7 +24,7 @@ def generate_dining_hall_categories(self, json_event, event): "Halal", "Kosher Station", "Flat Top Grill"] def sort_menu(menu): - return category_order.index(menu["category"].strip()) if menu["category"].strip() in category_order else 1000 + return category_order.index(menu["category"].strip()) if menu["category"].strip() in category_order else len(category_order) for json_menu in sorted(json_event["menu"], key=sort_menu): data = {"event": event, "category": json_menu["category"]} From 9a441fc46dd0ba55e55e15ded10b610f5f5ae79c Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 13 Mar 2024 14:24:33 -0400 Subject: [PATCH 216/305] Add back necessary ids --- src/category/serializers.py | 2 +- src/eatery/serializers.py | 15 +-------------- src/event/serializers.py | 8 +------- src/item/serializers.py | 4 +--- 4 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/category/serializers.py b/src/category/serializers.py index 56ee284..6af5027 100644 --- a/src/category/serializers.py +++ b/src/category/serializers.py @@ -21,4 +21,4 @@ class CategorySerializerOptimized(serializers.ModelSerializer): class Meta: model = Category - fields = ["category", "items"] + fields = ["id", "category", "items"] diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 39f674f..7318096 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -3,7 +3,7 @@ from event.models import Event from event.serializers import EventSerializer, EventSerializerSimple, EventSerializerOptimized from django.utils import timezone -from datetime import date, timedelta +from datetime import timedelta from time import mktime class EaterySerializer(serializers.ModelSerializer): @@ -31,19 +31,6 @@ class Meta: fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] class EaterySerializerOptimized(serializers.ModelSerializer): - id = serializers.IntegerField() - name = serializers.CharField() - menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") - image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") - location = serializers.CharField(allow_null=True) - campus_area = serializers.CharField(allow_null=True) - online_order_url = serializers.URLField(allow_null=True) - latitude = serializers.FloatField(allow_null=True) - longitude = serializers.FloatField(allow_null=True) - payment_accepts_meal_swipes = serializers.BooleanField(allow_null=True) - payment_accepts_brbs = serializers.BooleanField(allow_null=True) - payment_accepts_cash = serializers.BooleanField(allow_null=True) - events = EventSerializerOptimized(many=True, read_only=True) class Meta: diff --git a/src/event/serializers.py b/src/event/serializers.py index c195401..435a044 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -5,9 +5,7 @@ class EventSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False, read_only=True) - event_description = serializers.CharField( - allow_null=True, allow_blank=True, default=None - ) + event_description = serializers.CharField(allow_null=True, allow_blank=True, default=None) start = serializers.IntegerField() end = serializers.IntegerField() menu = CategorySerializer(many=True, read_only=True) @@ -21,10 +19,6 @@ class Meta: fields = ["id", "eatery", "event_description", "start", "end", "menu"] class EventSerializerOptimized(serializers.ModelSerializer): - id = serializers.IntegerField(required=False, read_only=True) - event_description = serializers.CharField(allow_null=True, allow_blank=True, default=None) - start = serializers.IntegerField() - end = serializers.IntegerField() menu = CategorySerializerOptimized(many=True, read_only=True) class Meta: diff --git a/src/item/serializers.py b/src/item/serializers.py index 9abc7e0..519109e 100644 --- a/src/item/serializers.py +++ b/src/item/serializers.py @@ -14,8 +14,6 @@ class Meta: fields = ['id', 'category', 'name'] class ItemSerializerOptimized(serializers.ModelSerializer): - name = serializers.CharField(default = "Item") - class Meta: model = Item - fields = ['name'] \ No newline at end of file + fields = ['id', 'name'] \ No newline at end of file From 30bedc24936e8294fe82b2e43659cff8577cc29f Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 13 Mar 2024 14:44:53 -0400 Subject: [PATCH 217/305] Fix small bugs --- src/category/controllers/populate_category.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index b28ecce..c518162 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -24,7 +24,8 @@ def generate_dining_hall_categories(self, json_event, event): "Halal", "Kosher Station", "Flat Top Grill"] def sort_menu(menu): - return category_order.index(menu["category"].strip()) if menu["category"].strip() in category_order else len(category_order) + order = category_order.find(menu["category"].strip()) + return order if order != -1 else len(category_order) for json_menu in sorted(json_event["menu"], key=sort_menu): data = {"event": event, "category": json_menu["category"]} From e6552cf7805e63dc6bfb9e30d0288153e728c610 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 13 Mar 2024 14:52:31 -0400 Subject: [PATCH 218/305] Fix small bug again --- src/category/controllers/populate_category.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index c518162..d75ae24 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -24,8 +24,10 @@ def generate_dining_hall_categories(self, json_event, event): "Halal", "Kosher Station", "Flat Top Grill"] def sort_menu(menu): - order = category_order.find(menu["category"].strip()) - return order if order != -1 else len(category_order) + try: + return category_order.index(menu["category"].strip()) + except ValueError: + return len(category_order) for json_menu in sorted(json_event["menu"], key=sort_menu): data = {"event": event, "category": json_menu["category"]} From a8a5dc0dfe178751144b28a828f757c5936f1181 Mon Sep 17 00:00:00 2001 From: Mateo Date: Wed, 20 Mar 2024 13:42:57 -0400 Subject: [PATCH 219/305] fix get_events by day --- src/eatery/serializers.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index bf56ef1..84ecd87 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -2,9 +2,8 @@ from eatery.models import Eatery from event.models import Event from event.serializers import EventSerializer, EventSerializerSimple, EventReadSerializer -from django.utils import timezone -from datetime import date, timedelta -from time import mktime +from datetime import timedelta, datetime +from zoneinfo import ZoneInfo class EaterySerializer(serializers.ModelSerializer): id = serializers.IntegerField() @@ -65,10 +64,12 @@ class EaterySerializerByDay(serializers.ModelSerializer): events = serializers.SerializerMethodField() def get_events(self, obj): - day = self.context.get("day") - today = (timezone.now() - timedelta(hours=5)).date() + timedelta(days=day) - today_unix = mktime(today.timetuple()) + timedelta(hours=5).seconds - events = Event.objects.filter(eatery=obj.id, start__gte=today_unix, start__lte=today_unix + timedelta(days=1).total_seconds()) + day_offset = self.context.get("day") + now = datetime.now(ZoneInfo("America/New_York")) + day = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=day_offset) + day_unix = int(day.timestamp()) + day_end_unix = int((day + timedelta(days=1)).timestamp()) + events = Event.objects.filter(eatery=obj.id, start__gte=day_unix, start__lt=day_end_unix) serializer = EventReadSerializer(instance=events, many=True) return serializer.data From a051996870e66e31a92d024be066408c0868a239 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 20 Mar 2024 17:29:59 -0400 Subject: [PATCH 220/305] Export default image url to constants --- src/eatery/models.py | 4 ++-- src/eatery/util/constants.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/eatery/models.py b/src/eatery/models.py index 7a65c08..c64b2db 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -1,6 +1,6 @@ from django.db import models from django.db import connection - +from util.constants import DEFAULT_IMAGE_URL class Eatery(models.Model): class CampusArea(models.TextChoices): @@ -12,7 +12,7 @@ class CampusArea(models.TextChoices): name = models.CharField(max_length=40) menu_summary = models.TextField(blank=True, null=True, default="") - image_url = models.URLField(blank=True, default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + image_url = models.URLField(blank=True, default=DEFAULT_IMAGE_URL) location = models.TextField(blank=True) campus_area = models.CharField( max_length=15, choices=CampusArea.choices, default=CampusArea.NONE, blank=True diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index a4c4af4..76e95ff 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -7,6 +7,8 @@ DAY_OF_WEEK_LIST = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] +DEFAULT_IMAGE_URL = "https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg" + class SnapshotFileName(Enum): EATERY_STORE = "eatery_store.txt" ALERT_STORE = "alert_store.txt" From 3c2a451cb0332076c8f9adc2ee02b887ee46a29e Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 20 Mar 2024 17:56:37 -0400 Subject: [PATCH 221/305] Fix small import bug --- src/eatery/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery/models.py b/src/eatery/models.py index c64b2db..731858a 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -1,6 +1,6 @@ from django.db import models from django.db import connection -from util.constants import DEFAULT_IMAGE_URL +from eatery.util.constants import DEFAULT_IMAGE_URL class Eatery(models.Model): class CampusArea(models.TextChoices): From 64e427b24739ad3acbcca6ffec58eb89dc0bfff7 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Wed, 20 Mar 2024 18:13:37 -0400 Subject: [PATCH 222/305] Deleting persons --- src/person/__init__.py | 0 src/person/admin.py | 5 ---- src/person/apps.py | 6 ----- src/person/migrations/0001_initial.py | 36 --------------------------- src/person/migrations/__init__.py | 0 src/person/models.py | 19 -------------- src/person/serializers.py | 14 ----------- src/person/urls.py | 11 -------- src/person/views.py | 11 -------- 9 files changed, 102 deletions(-) delete mode 100644 src/person/__init__.py delete mode 100644 src/person/admin.py delete mode 100644 src/person/apps.py delete mode 100644 src/person/migrations/0001_initial.py delete mode 100644 src/person/migrations/__init__.py delete mode 100644 src/person/models.py delete mode 100644 src/person/serializers.py delete mode 100644 src/person/urls.py delete mode 100644 src/person/views.py diff --git a/src/person/__init__.py b/src/person/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/person/admin.py b/src/person/admin.py deleted file mode 100644 index 8b935f3..0000000 --- a/src/person/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin -from person.models import Student, Chef - -admin.site.register(Student) -admin.site.register(Chef) \ No newline at end of file diff --git a/src/person/apps.py b/src/person/apps.py deleted file mode 100644 index b30fee4..0000000 --- a/src/person/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class PersonConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'person' diff --git a/src/person/migrations/0001_initial.py b/src/person/migrations/0001_initial.py deleted file mode 100644 index 0bbad96..0000000 --- a/src/person/migrations/0001_initial.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 4.0 on 2023-04-13 00:10 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('item', '0001_initial'), - ('eatery', '0001_initial'), - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='Student', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('net_id', models.TextField()), - ('favorite_eateries', models.ManyToManyField(related_name='student', to='eatery.Eatery')), - ('favorite_items', models.ManyToManyField(related_name='student', to='item.Item')), - ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), - ], - ), - migrations.CreateModel( - name='Chef', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('eateries_managed', models.ManyToManyField(related_name='chef', to='eatery.Eatery')), - ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), - ], - ), - ] diff --git a/src/person/migrations/__init__.py b/src/person/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/person/models.py b/src/person/models.py deleted file mode 100644 index 82067aa..0000000 --- a/src/person/models.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.db import models -from django.contrib.auth.models import User - - -class Student(models.Model): - net_id = models.TextField() - favorite_eateries = models.ManyToManyField('eatery.Eatery', related_name='student') - favorite_items = models.ManyToManyField('item.Item', related_name='student') - user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) - - def __str__(self): - return f"Student {self.net_id}" - -class Chef(models.Model): - eateries_managed = models.ManyToManyField('eatery.Eatery', related_name='chef') - user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) - - def __str__(self): - return f"Chef {self.user.username}" \ No newline at end of file diff --git a/src/person/serializers.py b/src/person/serializers.py deleted file mode 100644 index a5e3942..0000000 --- a/src/person/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -from rest_framework import serializers - -from person.models import Student, Chef -from django.contrib.auth.models import User - -class StudentSerializer(serializers.ModelSerializer): - class Meta: - model = Student - fields = ['id', 'net_id', 'user', 'favorite_eateries', 'favorite_items'] - -class ChefSerializer(serializers.ModelSerializer): - class Meta: - model = Chef - fields = ['id', 'user', 'eateries_managed'] diff --git a/src/person/urls.py b/src/person/urls.py deleted file mode 100644 index 75e30dc..0000000 --- a/src/person/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.urls import path, include -from person.views import StudentViewSet, ChefViewSet -from rest_framework.routers import DefaultRouter - -router = DefaultRouter() -router.register("student", StudentViewSet) -router.register("chef", ChefViewSet) - -urlpatterns = [ - path("", include(router.urls)), -] \ No newline at end of file diff --git a/src/person/views.py b/src/person/views.py deleted file mode 100644 index 778d6e7..0000000 --- a/src/person/views.py +++ /dev/null @@ -1,11 +0,0 @@ -from rest_framework import viewsets -from person.models import Student, Chef -from person.serializers import StudentSerializer, ChefSerializer - -class StudentViewSet(viewsets.ModelViewSet): - queryset = Student.objects.all() - serializer_class = StudentSerializer - -class ChefViewSet(viewsets.ModelViewSet): - queryset = Chef.objects.all() - serializer_class = ChefSerializer \ No newline at end of file From abd5e4656930713e0c140c49a8e6dd46945ca615 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Wed, 20 Mar 2024 18:13:37 -0400 Subject: [PATCH 223/305] Deleting persons --- src/person/__init__.py | 0 src/person/admin.py | 5 ---- src/person/apps.py | 6 ----- src/person/migrations/0001_initial.py | 36 --------------------------- src/person/migrations/__init__.py | 0 src/person/models.py | 19 -------------- src/person/serializers.py | 14 ----------- src/person/urls.py | 11 -------- src/person/views.py | 11 -------- 9 files changed, 102 deletions(-) delete mode 100644 src/person/__init__.py delete mode 100644 src/person/admin.py delete mode 100644 src/person/apps.py delete mode 100644 src/person/migrations/0001_initial.py delete mode 100644 src/person/migrations/__init__.py delete mode 100644 src/person/models.py delete mode 100644 src/person/serializers.py delete mode 100644 src/person/urls.py delete mode 100644 src/person/views.py diff --git a/src/person/__init__.py b/src/person/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/person/admin.py b/src/person/admin.py deleted file mode 100644 index 8b935f3..0000000 --- a/src/person/admin.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.contrib import admin -from person.models import Student, Chef - -admin.site.register(Student) -admin.site.register(Chef) \ No newline at end of file diff --git a/src/person/apps.py b/src/person/apps.py deleted file mode 100644 index b30fee4..0000000 --- a/src/person/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class PersonConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'person' diff --git a/src/person/migrations/0001_initial.py b/src/person/migrations/0001_initial.py deleted file mode 100644 index 0bbad96..0000000 --- a/src/person/migrations/0001_initial.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 4.0 on 2023-04-13 00:10 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('item', '0001_initial'), - ('eatery', '0001_initial'), - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.CreateModel( - name='Student', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('net_id', models.TextField()), - ('favorite_eateries', models.ManyToManyField(related_name='student', to='eatery.Eatery')), - ('favorite_items', models.ManyToManyField(related_name='student', to='item.Item')), - ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), - ], - ), - migrations.CreateModel( - name='Chef', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('eateries_managed', models.ManyToManyField(related_name='chef', to='eatery.Eatery')), - ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), - ], - ), - ] diff --git a/src/person/migrations/__init__.py b/src/person/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/person/models.py b/src/person/models.py deleted file mode 100644 index 82067aa..0000000 --- a/src/person/models.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.db import models -from django.contrib.auth.models import User - - -class Student(models.Model): - net_id = models.TextField() - favorite_eateries = models.ManyToManyField('eatery.Eatery', related_name='student') - favorite_items = models.ManyToManyField('item.Item', related_name='student') - user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) - - def __str__(self): - return f"Student {self.net_id}" - -class Chef(models.Model): - eateries_managed = models.ManyToManyField('eatery.Eatery', related_name='chef') - user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) - - def __str__(self): - return f"Chef {self.user.username}" \ No newline at end of file diff --git a/src/person/serializers.py b/src/person/serializers.py deleted file mode 100644 index a5e3942..0000000 --- a/src/person/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -from rest_framework import serializers - -from person.models import Student, Chef -from django.contrib.auth.models import User - -class StudentSerializer(serializers.ModelSerializer): - class Meta: - model = Student - fields = ['id', 'net_id', 'user', 'favorite_eateries', 'favorite_items'] - -class ChefSerializer(serializers.ModelSerializer): - class Meta: - model = Chef - fields = ['id', 'user', 'eateries_managed'] diff --git a/src/person/urls.py b/src/person/urls.py deleted file mode 100644 index 75e30dc..0000000 --- a/src/person/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.urls import path, include -from person.views import StudentViewSet, ChefViewSet -from rest_framework.routers import DefaultRouter - -router = DefaultRouter() -router.register("student", StudentViewSet) -router.register("chef", ChefViewSet) - -urlpatterns = [ - path("", include(router.urls)), -] \ No newline at end of file diff --git a/src/person/views.py b/src/person/views.py deleted file mode 100644 index 778d6e7..0000000 --- a/src/person/views.py +++ /dev/null @@ -1,11 +0,0 @@ -from rest_framework import viewsets -from person.models import Student, Chef -from person.serializers import StudentSerializer, ChefSerializer - -class StudentViewSet(viewsets.ModelViewSet): - queryset = Student.objects.all() - serializer_class = StudentSerializer - -class ChefViewSet(viewsets.ModelViewSet): - queryset = Chef.objects.all() - serializer_class = ChefSerializer \ No newline at end of file From fb3c7dff2bfbef0eff33b3e6e986e2b631e53925 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Wed, 20 Mar 2024 18:14:57 -0400 Subject: [PATCH 224/305] Deleting person remnants --- src/eatery_blue_backend/settings.py | 3 --- src/eatery_blue_backend/urls.py | 1 - 2 files changed, 4 deletions(-) diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 53dbae8..75fe88e 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -40,7 +40,6 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework.authtoken", - # Apps "eatery_blue_backend", "eatery", @@ -48,8 +47,6 @@ "report", "item", "category", - "person", - # Third party "rest_framework", ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index f289980..154f541 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -8,5 +8,4 @@ path("item/", include("item.urls")), path("category/", include("category.urls")), path("report/", include("report.urls")), - path("person/", include("person.urls")), ] From 3915db2b037bde62c866b410bc11a95afd7427bc Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Wed, 20 Mar 2024 18:14:57 -0400 Subject: [PATCH 225/305] Deleting person remnants --- src/eatery_blue_backend/settings.py | 3 --- src/eatery_blue_backend/urls.py | 1 - 2 files changed, 4 deletions(-) diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 53dbae8..75fe88e 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -40,7 +40,6 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework.authtoken", - # Apps "eatery_blue_backend", "eatery", @@ -48,8 +47,6 @@ "report", "item", "category", - "person", - # Third party "rest_framework", ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index f289980..154f541 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -8,5 +8,4 @@ path("item/", include("item.urls")), path("category/", include("category.urls")), path("report/", include("report.urls")), - path("person/", include("person.urls")), ] From f1d90f6b7cf6f69a8ebc2ab665e51bbc5c455519 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Wed, 20 Mar 2024 18:19:31 -0400 Subject: [PATCH 226/305] Intial commit --- src/user/__init__.py | 0 src/user/admin.py | 5 +++++ src/user/apps.py | 6 ++++++ src/user/migrations/__init__.py | 0 src/user/models.py | 3 +++ src/user/views.py | 3 +++ 6 files changed, 17 insertions(+) create mode 100644 src/user/__init__.py create mode 100644 src/user/admin.py create mode 100644 src/user/apps.py create mode 100644 src/user/migrations/__init__.py create mode 100644 src/user/models.py create mode 100644 src/user/views.py diff --git a/src/user/__init__.py b/src/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user/admin.py b/src/user/admin.py new file mode 100644 index 0000000..22c1186 --- /dev/null +++ b/src/user/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from user.models import User + +# Register your models here. +admin.site.register(User) diff --git a/src/user/apps.py b/src/user/apps.py new file mode 100644 index 0000000..36cce4c --- /dev/null +++ b/src/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user' diff --git a/src/user/migrations/__init__.py b/src/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user/models.py b/src/user/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/src/user/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/src/user/views.py b/src/user/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/src/user/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 8f504f2678dfbc200680925e820373d518b643db Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Wed, 20 Mar 2024 18:19:31 -0400 Subject: [PATCH 227/305] Intial commit --- src/user/__init__.py | 0 src/user/admin.py | 5 +++++ src/user/apps.py | 6 ++++++ src/user/migrations/__init__.py | 0 src/user/models.py | 3 +++ src/user/views.py | 3 +++ 6 files changed, 17 insertions(+) create mode 100644 src/user/__init__.py create mode 100644 src/user/admin.py create mode 100644 src/user/apps.py create mode 100644 src/user/migrations/__init__.py create mode 100644 src/user/models.py create mode 100644 src/user/views.py diff --git a/src/user/__init__.py b/src/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user/admin.py b/src/user/admin.py new file mode 100644 index 0000000..22c1186 --- /dev/null +++ b/src/user/admin.py @@ -0,0 +1,5 @@ +from django.contrib import admin +from user.models import User + +# Register your models here. +admin.site.register(User) diff --git a/src/user/apps.py b/src/user/apps.py new file mode 100644 index 0000000..36cce4c --- /dev/null +++ b/src/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user' diff --git a/src/user/migrations/__init__.py b/src/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user/models.py b/src/user/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/src/user/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/src/user/views.py b/src/user/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/src/user/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From b544a7787124760d48df9a7d8352a3e179f21e27 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Sun, 24 Mar 2024 13:19:03 -0400 Subject: [PATCH 228/305] Creating users model --- src/item/models.py | 10 ++++++---- src/user/models.py | 13 +++++++++++++ src/user/serializers.py | 0 src/user/urls.py | 0 4 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 src/user/serializers.py create mode 100644 src/user/urls.py diff --git a/src/item/models.py b/src/item/models.py index 223539d..885d9ad 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -1,11 +1,13 @@ from django.db import models -from eatery.models import Eatery from category.models import Category + class Item(models.Model): - category = models.ForeignKey(Category, related_name = "items", on_delete=models.DO_NOTHING) - name = models.CharField(max_length=40, default = "Item") + category = models.ForeignKey( + Category, related_name="items", on_delete=models.DO_NOTHING + ) + name = models.CharField(max_length=40, default="Item") base_price = models.FloatField(null=True, blank=True, default=0.0) - + def __str__(self): return f"{self.name} ({self.category.name})" diff --git a/src/user/models.py b/src/user/models.py index 71a8362..ce9503c 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -1,3 +1,16 @@ from django.db import models + # Create your models here. +class User(models.Model): + netid = models.CharField(max_length=40, default="User") + token = models.CharField(max_length=40, default="User") + name = models.CharField(max_length=40, default="User") + favorite_items = models.ManyToManyField( + "item.Item", related_name="favorited_by", blank=True + ) + favorite_eateries = models.ManyToManyField( + "eatery.Eatery", related_name="favorited_by", blank=True + ) + is_admin = models.BooleanField(default=False) + last_active = models.DateTimeField(auto_now=True) diff --git a/src/user/serializers.py b/src/user/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user/urls.py b/src/user/urls.py new file mode 100644 index 0000000..e69de29 From 807fc3d6831bf230221e96ae4779e53f9d21d2a1 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Sun, 24 Mar 2024 13:19:03 -0400 Subject: [PATCH 229/305] Creating users model --- src/item/models.py | 10 ++++++---- src/user/models.py | 13 +++++++++++++ src/user/serializers.py | 0 src/user/urls.py | 0 4 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 src/user/serializers.py create mode 100644 src/user/urls.py diff --git a/src/item/models.py b/src/item/models.py index 223539d..885d9ad 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -1,11 +1,13 @@ from django.db import models -from eatery.models import Eatery from category.models import Category + class Item(models.Model): - category = models.ForeignKey(Category, related_name = "items", on_delete=models.DO_NOTHING) - name = models.CharField(max_length=40, default = "Item") + category = models.ForeignKey( + Category, related_name="items", on_delete=models.DO_NOTHING + ) + name = models.CharField(max_length=40, default="Item") base_price = models.FloatField(null=True, blank=True, default=0.0) - + def __str__(self): return f"{self.name} ({self.category.name})" diff --git a/src/user/models.py b/src/user/models.py index 71a8362..ce9503c 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -1,3 +1,16 @@ from django.db import models + # Create your models here. +class User(models.Model): + netid = models.CharField(max_length=40, default="User") + token = models.CharField(max_length=40, default="User") + name = models.CharField(max_length=40, default="User") + favorite_items = models.ManyToManyField( + "item.Item", related_name="favorited_by", blank=True + ) + favorite_eateries = models.ManyToManyField( + "eatery.Eatery", related_name="favorited_by", blank=True + ) + is_admin = models.BooleanField(default=False) + last_active = models.DateTimeField(auto_now=True) diff --git a/src/user/serializers.py b/src/user/serializers.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user/urls.py b/src/user/urls.py new file mode 100644 index 0000000..e69de29 From ddd28bfdae75b387877bb6cd3fbc96b1694b3ac1 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Tue, 26 Mar 2024 20:28:37 -0400 Subject: [PATCH 230/305] Fix empty menu issue --- src/category/controllers/populate_category.py | 9 +++-- src/eatery/util/eatery_store.txt | 36 +------------------ src/event/controllers/populate_event.py | 2 +- src/item/controllers/populate_item.py | 9 +++-- .../cornell_dining_now_eateries.json | 1 - 5 files changed, 14 insertions(+), 43 deletions(-) delete mode 100644 src/static_sources/cornell_dining_now_eateries.json diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index d75ae24..e4bede6 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -1,6 +1,7 @@ from category.models import Category from category.serializers import CategorySerializer from item.models import Item +import json """ Add categories to Category Model from CornellDiningNow. @@ -77,6 +78,10 @@ def process(self, events_dict, json_eateries): categories_dict = {} + with open("./static_sources/external_eateries.json", "r") as file: + json_obj = json.load(file) + json_eateries += json_obj["eateries"] + for json_eatery in json_eateries: eatery_id = int(json_eatery["id"]) categories_dict[eatery_id] = {} @@ -87,9 +92,7 @@ def process(self, events_dict, json_eateries): else: continue - is_cafe = "Cafe" in { - eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] - } + is_cafe = not "Dining Room" in {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} """ For every event in an eatery --> for every menu in an eatery --> get categories diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index 7f9d817..9488a26 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -1,40 +1,6 @@ - -{"id": 1, "menu_summary": "Kosher food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg"} -{"id": 2, "menu_summary": "Noodles, sandwiches, coffee", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg"} -{"id": 3, "menu_summary": "Build your own bowl", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg"} -{"id": 4, "menu_summary": "Quick and convenient food and snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg"} -{"id": 5, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg"} -{"id": 6, "menu_summary": "Sandwiches, salads, hay", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg"} -{"id": 7, "menu_summary": "Bagels, bagels, bagels", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg"} -{"id": 8, "menu_summary": "Sandwiches, salads, drinks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg"} -{"id": 10, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg"} -{"id": 11, "menu_summary": "Ice cream and more ice cream", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg"} -{"id": 12, "menu_summary": "Smoothies, quesadillas, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg"} -{"id": 13, "menu_summary": "Fries, burgers, and wraps", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg"} -{"id": 14, "menu_summary": "Sandwiches, salads, goldfish", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg"} -{"id": 15, "menu_summary": "Fire, scales, green dragons", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg"} -{"id": 16, "menu_summary": "Hotdogs, hotdogs, and hotdogs", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg"} -{"id": 17, "menu_summary": "Ice cream, slushies, and fried dough", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg"} -{"id": 18, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg"} -{"id": 19, "menu_summary": "Hot Sandwiches and convenience items", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg"} -{"id": 20, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg"} -{"id": 21, "menu_summary": "Sandwiches, soups, coffee", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg"} -{"id": 22, "menu_summary": "Salads, wraps, coffee", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg"} -{"id": 23, "menu_summary": "Sandwiches, soups, sushi", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg"} -{"id": 24, "menu_summary": "Sandwiches, salads, wraps", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg"} -{"id": 25, "menu_summary": "Freshly remodeled dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg"} -{"id": 26, "menu_summary": "The only central campus dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg"} -{"id": 27, "menu_summary": "Gluten free dining hall", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg"} -{"id": 29, "menu_summary": "A west dining classic", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg"} -{"id": 30, "menu_summary": "Coffee, tea, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg"} -{"id": 31, "menu_summary": "Soups, salads, snacks", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg"} -{"id": 32, "menu_summary": "Burgers, pasta, quesadillas", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg"} {"id": 33, "name": "Terrace", "menu_summary": "Burrito and rice bowls, pho", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg", "location": "Statler Hall", "campus_area": "Central", "latitude": 42.446267, "longitude": -76.482314, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} {"id": 34, "name": "Mac's Caf\u00e9", "menu_summary": "Flatbreads, pasta, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Macs-Cafe.jpg", "location": "Statler Hotel", "campus_area": "Central", "latitude": 42.445921, "longitude": -76.481984, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": true, "payment_accepts_cash": true, "online_order_url": null} {"id": 35, "name": "Temple of Zeus", "menu_summary": "Coffee, pastries, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Zeus.jpg", "location": "Goldwin Smith Hall", "campus_area": "Central", "latitude": 42.449091, "longitude": -76.483414, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 36, "name": "Gimme Coffee", "menu_summary": "Coffee, pastries, tea", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Gimme-Coffee.jpg", "location": "Gates Hall", "campus_area": "Central", "latitude": 42.444958, "longitude": -76.481169, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} -{"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} -{"id": 39, "menu_summary": "Pizza, pasta, wok, halal, kosher", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg"} -{"id": 40, "menu_summary": "Baked Goods, Coffee, Sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/novicks-cafe.jpg"} -{"id": 41, "menu_summary": "Grab-n-Go, American, kosher", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/vets-cafe.jpg"} +{"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} \ No newline at end of file diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py index 4409493..8823920 100644 --- a/src/event/controllers/populate_event.py +++ b/src/event/controllers/populate_event.py @@ -72,7 +72,7 @@ def generate_external_events(self, json_eatery): print(event.errors) return event.errors - events.append(event.data["id"]) + events.append(event.data["id"]) return events def process(self, json_eateries): diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 2b22aac..78f048f 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -3,6 +3,7 @@ from eatery.models import Eatery from eatery.serializers import EaterySerializer import string +import json class PopulateItemController(): def __init__(self): @@ -45,6 +46,10 @@ def generate_dining_hall_items(self, menu, json_event, json_eatery): print(item.errors) def process(self, categories_dict, json_eateries): + with open("./static_sources/external_eateries.json", "r") as file: + json_obj = json.load(file) + json_eateries += json_obj["eateries"] + for json_eatery in json_eateries: if int(json_eatery["id"]) in categories_dict: eatery_menus = categories_dict[int(json_eatery["id"])] @@ -54,9 +59,7 @@ def process(self, categories_dict, json_eateries): iter = list(eatery_menus.keys()) i = 0 - is_cafe = "Cafe" in { - eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] - } + is_cafe = not "Dining Room" in {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} json_dates = json_eatery["operatingHours"] for json_date in json_dates: diff --git a/src/static_sources/cornell_dining_now_eateries.json b/src/static_sources/cornell_dining_now_eateries.json deleted file mode 100644 index 2d2ac1b..0000000 --- a/src/static_sources/cornell_dining_now_eateries.json +++ /dev/null @@ -1 +0,0 @@ -{"status":"success","data":{"eateries":[{"id":31,"slug":"104-West","name":"104West!","nameshort":"104West!","about":"
\r\nLocated next to the Center for Jewish Living building on the south edge of west campus, 104West!<\/strong> is Cornell's kosher and multicultural
dining room<\/a>. Menus are prepared under the supervision of STAR-K (meat and pareve) and STAR-D (dairy) Kosher Certifications, and Jewish dietary laws are strictly followed with the direction of a resident \"Mashgiach,\" or kosher-food supervisor.\r\n

\r\nYou don't have to keep kosher to enjoy the menu\u2013come sample traditional kosher entrees and enjoy mouth-watering ethnic and international options with a kosher flair. Dining options also include Halal, Seventh-day Adventist, vegetarian, vegan, and other diets.\r\n

\r\nShabbat dinner times vary over the course of the year, based on sunset times. Save money and help us plan by making advance reservations for Shabbat dinners and holiday meals at
https:\/\/kosher.scl.cornell.edu<\/a>!\r\n

\r\nMore Info:
104West!<\/strong><\/a>\r\n

\r\n
\"104West!<\/a>\r\n

","aboutshort":"Cornell's kosher and multicultural dining room is STAR-K and STAR-D certified.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"vlpa2hk9677m9bcbh6n2dtpn7k@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-272-6907","contactEmail":null,"serviceUnitId":9,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.444266,"longitude":-76.487598,"location":"104 West Avenue","coordinates":{"latitude":42.444266,"longitude":-76.487598},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":7,"slug":"Amit-Bhatia-Libe-Cafe","name":"Amit Bhatia Libe Caf\u00e9","nameshort":"Amit Bhatia Libe Caf\u00e9","about":"
A combined effort between Cornell Dining and Olin Library, the Amit Bhatia Libe Caf\u00e9<\/strong> serves specialty Starbucks coffees, smoothies, pastries, and Freshtake Grab-n-Go sandwiches, salads, and snacks.\r\n

\r\n\r\nMore Info:
Amit Bhatia Libe Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Amit<\/a>\r\n

","aboutshort":"The perfect place to take a study break, or to enjoy a latte and a pastry while you enjoy wireless Internet access on your laptop.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"g1pfs9edl1ks5o2dbc58e7fhm8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-4344","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448019,"longitude":-76.484499,"location":"Olin Library","coordinates":{"latitude":42.448019,"longitude":-76.484499},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640124000,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640210400,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640296800,"start":"8:00am","end":"5:00pm","menu":[],"calSummary":"Open until 6:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":8,"slug":"Atrium-Cafe","name":"Atrium Caf\u00e9","nameshort":"Atrium Caf\u00e9","about":"
The Atrium Caf\u00e9's<\/strong> coffee kiosk proudly serves Starbucks specialty coffee, pastries, and Grab-n-Go items, with extended hours. When class is in session, the coffee kiosk opens weekdays at 7am, and closes at 4pm Monday through Thursday, and 2pm Friday.\r\n

\r\nMore Info:
Atrium Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Atrium<\/a>\r\n

","aboutshort":"The Atrium Caf\u00e9 is located in historic Sage Hall, home to the Johnson Graduate School of Management. Coffee kiosk is open extended hours!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"9g3c81c0p2loacsbvrjj5o371c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7591","contactEmail":null,"serviceUnitId":9999,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.445784,"longitude":-76.483232,"location":"Sage Hall","coordinates":{"latitude":42.445784,"longitude":-76.483232},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":1,"slug":"Bear-Necessities","name":"Bear Necessities Grill & C-Store","nameshort":"Bear Necessities","about":"
The grill serves up deluxe burgers, hot chicken sandwiches, delicious french fries, and other mouth-watering comfort foods. You can also order 5 Star Subs and homemade pizza. Bear Necessities<\/strong> also has a convenience store with staple food, beverages and household items.\r\n

\r\n\r\nMore Info:
Bear Necessities<\/strong><\/a>\r\n

\r\n
\"Bear<\/a>\r\n

","aboutshort":"Bear Necessities is a convenience store and grill located on the first floor of Robert Purcell Community Center on north campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h319a8fk4b5lv0644ebkskhha8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-254-8227","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455777,"longitude":-76.477657,"location":"RPCC - first floor","coordinates":{"latitude":42.455777,"longitude":-76.477657},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640134800,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640221200,"start":"8:00am","end":"8:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00am"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Convenience Store","descrshort":"convenience store"}],"diningCuisines":[{"name":"Pizza","nameshort":"Pizza","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Grill - 5-Star Subs","category":"Grill","item":"5-Star Subs","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Pizza","category":"Pasta & Pizza","item":"Pizza","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Pasta & Pizza - Calzones","category":"Pasta & Pizza","item":"Calzones","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Misc - Mozzarella Sticks","category":"Misc","item":"Mozzarella Sticks","healthy":false,"showCategory":false},{"descr":"Misc - Onion Petals","category":"Misc","item":"Onion Petals","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":25,"slug":"Becker-House-Dining","name":"Becker House Dining Room","nameshort":"Becker House Dining","about":"
\r\nThe Becker House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
Becker House Dining Room<\/strong><\/a>\r\n

\r\n
\"Becker<\/a>\r\n

","aboutshort":"Dining room located in Carl Becker House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"di2s9rofto7m8innt5e8vftl0o@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8882","contactEmail":null,"serviceUnitId":2,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.448336,"longitude":-76.489606,"location":"Carl Becker House","coordinates":{"latitude":42.448336,"longitude":-76.489606},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":10,"slug":"Big-Red-Barn","name":"Big Red Barn","nameshort":"Big Red Barn","about":"
\r\nThe Big Red Barn<\/strong> is a cozy caf\u00e9 with a delicious breakfast and lunch menu. It also serves as Cornell's on-campus social center for graduate and professional students. Relax at outdoor picnic tables, or sit inside by the fireplace in comfortable lounge furniture.\r\n

\r\nMore Info:
Big Red Barn<\/strong><\/a>\r\n

\r\n
\"Big<\/a>\r\n

","aboutshort":"Once a carriage house, the Big Red Barn is now a cozy caf\u00e9 with a delicious breakfast and lunch menu.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"u1kmovdep2qlmr86io8h4p3ee8@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0428","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448526,"longitude":-76.48098,"location":"Big Red Barn","coordinates":{"latitude":42.448526,"longitude":-76.48098},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":11,"slug":"Bug-Stop-Bagels","name":"Bus Stop Bagels","nameshort":"Bus Stop Bagels","about":"
\r\nBus Stop Bagels<\/strong>, next to the Trillium food court in Kennedy Hall, gets fresh bagels delivered twice a day from Ithaca Bakery with all the usual toppings and a great selection of house sandwiches, plus Starbucks coffee drinks and snack foods.\r\n

\r\nMore Info:
Bus Stop Bagels<\/strong><\/a>\r\n

\r\n
\"Bus<\/a>\r\n

","aboutshort":"Bagels for breakfast, bagels for lunch, bagels to go!","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"rpp0nrlp282t9h18hhol5f0dkc@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447839,"longitude":-76.479456,"location":"Kennedy Hall","coordinates":{"latitude":42.447839,"longitude":-76.479456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640116800,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640203200,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640289600,"start":"7:00am","end":"3:00pm","menu":[],"calSummary":"Open until 3pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Coffee Bar - 96oz Coffee2Go","category":"Coffee Bar","item":"96oz Coffee2Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":12,"slug":"Cafe-Jennie","name":"Caf\u00e9 Jennie","nameshort":"Caf\u00e9 Jennie","about":"
\r\nLocated in The Cornell Store, Caf\u00e9 Jennie is named for Jennie McGraw, the daughter of John McGraw, a wealthy industrialist and a founding Cornell Trustee. Stop by for a Peet's Coffee, delicious Cheesecake Factory baked goods, and a mouth-watering array of sandwiches and wraps.\r\n

\r\nMore Info:
Caf\u00e9 Jennie<\/strong><\/a>\r\n

\r\n
\"Caf\u00e9<\/a>\r\n

","aboutshort":"A caf\u00e9 and sandwich\/pastry shop located in The Cornell Store.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"geron0aq1ooj7jugmcmdc2s2cc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-8095","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446851,"longitude":-76.484376,"location":"The Cornell Store","coordinates":{"latitude":42.446851,"longitude":-76.484376},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640098800,"endTimestamp":1640120400,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640185200,"endTimestamp":1640206800,"start":"10:00am","end":"4:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640271600,"endTimestamp":1640286000,"start":"10:00am","end":"2:00pm","menu":[],"calSummary":"Open until 4:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Javiva","category":"Coffee Bar","item":"Javiva","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":2,"slug":"Carols-Cafe","name":"Carol's Caf\u00e9","nameshort":"Carol's Caf\u00e9","about":"
\r\nCome visit Carol's Caf\u00e9<\/strong> and enjoy a menu featuring specialty coffee from Starbucks, smoothies, hot daily soup selections, sushi, fresh fruit and snacks, Freshtake Grab-n-Go sandwiches and salads, delicious pastries and baked goods and more.\r\n

\r\nMore Info:
Carol's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Carol's<\/a>\r\n

","aboutshort":"Warm, inviting caf\u00e9 at Carol Tatkon Center open to the whole campus community.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"05r2nhfjbnknmsccgd6u8dij0g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2257","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453236,"longitude":-76.479404,"location":"Carol Tatkon Center, Balch Hall","coordinates":{"latitude":42.453236,"longitude":-76.479404},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":26,"slug":"Cook-House-Dining","name":"Cook House Dining Room","nameshort":"Cook House Dining","about":"
\r\nThe Cook House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. The eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Cook House Dining Room<\/strong><\/a>\r\n

\r\n
\"Cook<\/a>\r\n

","aboutshort":"Dining room located in Alice Cook House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"27hli58rto1hpf15m3sbe54sak@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-9508","contactEmail":null,"serviceUnitId":1,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.44889,"longitude":-76.488919,"location":"Alice Cook House","coordinates":{"latitude":42.44889,"longitude":-76.488919},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":14,"slug":"Cornell-Dairy-Bar","name":"Cornell Dairy Bar","nameshort":"Cornell Dairy Bar","about":"
\r\nIn the beautifully renovated Stocking Hall on the east end of Tower Road, the Cornell Dairy Bar is a great place for breakfast, coffee, or even sweet treat like Cornell ice cream, sundaes and floats. Regular hours vary seasonally, especially weekend hours, so please check the specific hours on this page for updates.\r\n

\r\nMore Info:
Cornell Dairy Bar<\/strong><\/a>\r\n

\r\n
\"Cornell<\/a>\r\n

","aboutshort":"In the renovated Stocking Hall, the Cornell Dairy features a variety of delicious Cornell Dairy ice cream, sundaes, and floats.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"prvu4v0nr4eu94mqu9q7busa6g@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-7660","contactEmail":"dairybar@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447301,"longitude":-76.471398,"location":"Stocking Hall","coordinates":{"latitude":42.447301,"longitude":-76.471398},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 5:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Chili","category":"Soup & Salad","item":"Chili","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"icecream-pink"},{"id":41,"slug":"Crossings-Cafe","name":"Crossings Caf\u00e9","nameshort":"Crossings Caf\u00e9","about":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","aboutshort":"Enjoy a sandwich, salad, or breakfast wrap or a handmade coffee drink!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"loadevpceh49fl3c8cuheb2dvk@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.4558,"longitude":-76.479386,"location":"Toni Morrison Hall","coordinates":{"latitude":42.4558,"longitude":-76.479386},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640124000,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640210400,"start":"7:00am","end":"5:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false},{"descr":"Mexican - Quesadillas","category":"Mexican","item":"Quesadillas","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Peet's Coffee","category":"Coffee Bar","item":"Peet's Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":32,"slug":"frannys","name":"Franny's","nameshort":"Franny's","about":"One of Cornell's newest eateries is Franny's, a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls. You'll find a unique mix of Pan-Asian sandwiches, rice bowls and ramen, bao buns and fries, as well as refreshing Asian-inspired drinks.","aboutshort":"Franny's is a food truck named for a beloved Architecture alumna, located between Milstein and Sibley Halls, featuring a unique mix of Asian-inspired cuisine.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekdays","googleCalendarId":"cornell.edu_26ui0ai54lp0cdp2m3j27ci86c@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-0293","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.451053,"longitude":-76.483884,"location":"Next to Sibley Hall","coordinates":{"latitude":42.451053,"longitude":-76.483884},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[{"name":"Asian","nameshort":"Asian","descr":""}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false},{"descr":"Asian - Rice Bowls","category":"Asian","item":"Rice Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Noodle Bowls","category":"Asian","item":"Noodle Bowls","healthy":false,"showCategory":false},{"descr":"Asian - Banh Mi Sandwiches","category":"Asian","item":"Banh Mi Sandwiches","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":16,"slug":"Goldies-Cafe","name":"Goldie's Caf\u00e9","nameshort":"Goldie's Caf\u00e9","about":"
\r\nGoldie's is a great location on Central Campus for breakfast, lunch, or a mid-day snack. Signature sandwiches \u2013 some served on German-style pretzel rolls \u2013 have become customer favorites, and a wide array of Freshtake Grab-n-Go items, snacks, and desserts are always on the menu.\r\n

\r\nMore Info:
Goldie's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Goldie's<\/a>\r\n

","aboutshort":"Conveniently located in the Physical Sciences Building on Central Campus.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"kb9ce5jj2f6oli3c90tc7j6peo@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-6775","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450064,"longitude":-76.481503,"location":"Physical Sciences Building","coordinates":{"latitude":42.450064,"longitude":-76.481503},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 7:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Frappuccino","category":"Coffee Bar","item":"Frappuccino","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Deli and Signature Sandwiches","category":"Deli","item":"Deli and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":15,"slug":"Green-Dragon","name":"Green Dragon","nameshort":"Green Dragon","about":"
\r\nStop by the Green Dragon<\/strong> and enjoy a hot specialty Finger Lakes Coffee Roasters coffee, Freshtake Grab-n-Go sandwiches and salads, kosher items, and delicious desserts.\r\n

\r\nMore Info:
Green Dragon<\/strong><\/a>\r\n

\r\n
\"Green<\/a>\r\n

","aboutshort":"A hot spot on central campus, especially for the students, faculty, and staff of College of Architecture, Art and Planning.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"7sii70faon9ta2vpoehr69415s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3327","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450948,"longitude":-76.484456,"location":"Sibley Hall","coordinates":{"latitude":42.450948,"longitude":-76.484456},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Finger Lakes Specialty Coffees","category":"Coffee Bar","item":"Finger Lakes Specialty Coffees","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Mighty Leaf Tea","category":"Coffee Bar","item":"Mighty Leaf Tea","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":24,"slug":"Hot-Dog-Cart","name":"Hot Dog Cart","nameshort":"Hot Dog Cart","about":"
\r\nCornell Dining's Hot Dog Cart<\/strong> is usually open outside Day Hall on summer weekdays when weather permits. On special occasions, the Hot Dog Cart may be elsewhere on campus. Keep an eye on Cornell Dining's
Facebook<\/a> and Twitter<\/a> for updates.\r\n

\r\nMore Info:
Hot Dog Cart<\/strong><\/a>\r\n

\r\n
\"Hot<\/a>\r\n

","aboutshort":"Enjoy lunch al fresco when weather permits, choosing an all-beef hot dog or vegetarian (tofu) hot dog.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"11:00am - 2:00pm weekdays, weather permitting","googleCalendarId":"cornell.edu_eaq3euadrebh0dmgqt618l7tgs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Grill - Hot Dogs","category":"Grill","item":"Hot Dogs","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"foodtruck-orange"},{"id":34,"slug":"icecreamcart","name":"Ice Cream Bike","nameshort":"Ice Cream Bike","about":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","aboutshort":"Fresh Cornell Dairy ice cream at Cornell Dining's Ice Cream Bike outside Day Hall.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":"Weekday middays for the summer, and Friday evenings for Cornell's concert series.","googleCalendarId":"cornell.edu_3kuppj4nsjes2b42jhpno5u97g@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447364,"longitude":-76.482907,"location":"Day Hall","coordinates":{"latitude":42.447364,"longitude":-76.482907},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cart","descrshort":"cart"}],"diningCuisines":[],"payMethods":[{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"icecream-pink"},{"id":27,"slug":"Jansens-Dining","name":"Jansen's Dining Room at Bethe House","nameshort":"Jansen's Dining","about":"
\r\nJansen's Dining Room at Bethe House<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Chef Jacob Kuehn puts together exciting new menus throughout the year. This eatery serves continental breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Jansen's Dining Room at Bethe House<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Dining room located in Hans Bethe House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"h0nfohf0d90ot1rmukjphj7ajc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1736","contactEmail":null,"serviceUnitId":5,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447116,"longitude":-76.48864,"location":"Hans Bethe House","coordinates":{"latitude":42.447116,"longitude":-76.48864},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":28,"slug":"Jansens-Market","name":"Jansen's Market","nameshort":"Jansen's Market","about":"
\r\nIn addition to an array of snacks, beverages, fresh and frozen take-away meals, household items, and pharmacy and beauty supplies, Jansen's Market<\/strong> also serve Starbucks coffee, bubble tea, smoothies, pastries, frozen yogurt, and Dreamfactory cheesecake.\r\n

\r\nMore Info:
Jansen's Market<\/strong><\/a>\r\n

\r\n
\"Jansen's<\/a>\r\n

","aboutshort":"Full-service convenience store located on the first floor of Noyes Community Recreation Center on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"0dqnc6l2mt25okch8nimsnojhg@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":"607-255-4997","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446325,"longitude":-76.487932,"location":"Noyes Community Recreation Center","coordinates":{"latitude":42.446325,"longitude":-76.487932},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Convenience Store","descrshort":"convenience store"},{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Smoothies","category":"Coffee Bar","item":"Smoothies","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Bubble Tea","category":"Coffee Bar","item":"Bubble Tea","healthy":false,"showCategory":false},{"descr":"Misc - Peanut Butter Sandwich Bar","category":"Misc","item":"Peanut Butter Sandwich Bar","healthy":false,"showCategory":false}],"announcements":[],"icon":"grocery-cyan"},{"id":29,"slug":"Keeton-House-Dining","name":"Keeton House Dining Room","nameshort":"Keeton House Dining","about":"
\r\nThe Keeton House Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Sample Chef Nery's unique menu offerings in the entr\u00e9e station, Asian station, grill, deli, and salad bar. This eatery serves hot breakfast on weekdays, brunch\/lunch on weekends, and dinner seven nights a week.\r\n

\r\nMore Info:
Keeton House Dining Room<\/strong><\/a>\r\n

\r\n
\"Keeton<\/a>\r\n

","aboutshort":"Dining room located in William Keeton House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ekd72jfc2qai617oloa2b0ibp0@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3033","contactEmail":null,"serviceUnitId":3,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.446942,"longitude":-76.489992,"location":"William Keeton House","coordinates":{"latitude":42.446942,"longitude":-76.489992},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":42,"slug":"Mann-Cafe","name":"Mann Caf\u00e9","nameshort":"Mann Caf\u00e9","about":"Take a study break at Mann Caf\u00e9!","aboutshort":"Take a study break at Mann Caf\u00e9!","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ppbukfcjo05629gk0t4fu26aro@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/full\/food_home.php","contactPhone":null,"contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.448799,"longitude":-76.47851,"location":"Mann Library on the Ag Quad","coordinates":{"latitude":42.448799,"longitude":-76.47851},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640116800,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640203200,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640289600,"start":"8:00am","end":"3:00pm","menu":[],"calSummary":"Open until 10:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Mexican - Burritos","category":"Mexican","item":"Burritos","healthy":false,"showCategory":false},{"descr":"Breakfast - Bagel Sandwiches","category":"Breakfast","item":"Bagel Sandwiches","healthy":false,"showCategory":false},{"descr":"Deli - Panini and Signature Sandwiches","category":"Deli","item":"Panini and Signature Sandwiches","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":18,"slug":"Marthas-Cafe","name":"Martha's Caf\u00e9","nameshort":"Martha's Cafe","about":"
\r\nMartha's Caf\u00e9<\/strong>, in Martha Van Rensselaer Hall, offers made-to-order Mediterranean-inspired grain and salad bowls and wraps, composed bowls, Copper Horse Coffee, and breakfast!\r\n

\r\nMore Info:
Martha's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Martha's<\/a>\r\n

","aboutshort":"Fresh food with a Mediterranean flair.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sperf092mrbt796rr36toeqrus@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-8080","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.450115,"longitude":-76.479237,"location":"Martha Van Rensselaer Hall","coordinates":{"latitude":42.450115,"longitude":-76.479237},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640091600,"endTimestamp":1640113200,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640178000,"endTimestamp":1640199600,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640264400,"endTimestamp":1640286000,"start":"8:00am","end":"2:00pm","menu":[],"calSummary":"Open until 6:30pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Milk","category":"Cornell Dairy","item":"Milk","healthy":false,"showCategory":true},{"descr":"Coffee Bar - Teatulia Teas","category":"Coffee Bar","item":"Teatulia Teas","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Copper Horse Coffee","category":"Coffee Bar","item":"Copper Horse Coffee","healthy":false,"showCategory":false},{"descr":"Lunch - Hot and Cold Mediterranean Bowls","category":"Lunch","item":"Hot and Cold Mediterranean Bowls","healthy":true,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":19,"slug":"Mattins-Cafe","name":"Mattin's Caf\u00e9","nameshort":"Mattin's Caf\u00e9","about":"
\r\nYou\u2019ll find Mattin's Caf\u00e9<\/strong> in the atrium of Duffield Hall, adjacent to Phillips Hall on the Engineering Quad. Enjoy made-to-order deli sandwiches, boneless wings, hot Starbucks coffee, Freshtake Grab-n-Go items, soups, kosher items, mouth-watering pastries and more.\r\n

\r\nMore Info:
Mattin's Caf\u00e9<\/strong><\/a>\r\n

\r\n
\"Mattin's<\/a>\r\n

","aboutshort":"Popular with engineers and a go to spot in the stunning atrium of Duffield Hall.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"1qman2n728pqjuq5ntaoofc7v0@group.calendar.google.com","onlineOrdering":true,"onlineOrderUrl":"https:\/\/get.cbord.com\/cornell\/","contactPhone":"607-255-4581","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.444162,"longitude":-76.482287,"location":"Duffield Hall","coordinates":{"latitude":42.444162,"longitude":-76.482287},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640088000,"endTimestamp":1640113200,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640174400,"endTimestamp":1640199600,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Open","startTimestamp":1640260800,"endTimestamp":1640286000,"start":"7:00am","end":"2:00pm","menu":[],"calSummary":"Open until 2:00pm"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Deli - Hot and Cold Deli Sandwiches","category":"Deli","item":"Hot and Cold Deli Sandwiches","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false}],"announcements":[],"icon":"fastfood-blue"},{"id":33,"slug":"mccormicks","name":"McCormick's at Moakley House","nameshort":"McCormick's","about":"McCormick's at Moakley House offers a casual environment close to campus, open daily through the season. Treat a campus visitor to an impeccably served luncheon, stop for refreshments after a round of golf, meet coworkers at the end of the day for a burger and a beer, or bring the family for a relaxing weekend brunch.\r\n\r\nNamed for Jack McCormick, a varsity golfer from the Cornell Class of 1957 whose will included a bequest to modernize Moakley House, this eatery alongside the award-winning Robert Trent Jones Golf Course at Cornell University isn't just for golfers. It's open to the public and has plenty of parking, and we welcome everyone for a drink, a snack, or a meal.\r\n\r\nMcCormick's is closed for the 2021 season as of September 24th.\r\n\r\n

\r\nMore Info:
McCormick's at Moakley House<\/strong><\/a>\r\n

","aboutshort":"Named for Jack McCormick, a varsity golfer from the Cornell Class of 1957, McCormick's is open to the public and has plenty of parking.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"cornell.edu_gqlmrg7n0qk8qihl75ru2u1p80@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-6536","contactEmail":"mccormicks@cornell.edu","serviceUnitId":null,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.458324,"longitude":-76.469539,"location":"Robert Trent Jones Golf Course","coordinates":{"latitude":42.458324,"longitude":-76.469539},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"}],"diningItems":[{"descr":"Grill - Chicken Sandwiches","category":"Grill","item":"Chicken Sandwiches","healthy":false,"showCategory":false},{"descr":"Grill - Burgers","category":"Grill","item":"Burgers","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Coffee (Hot)","category":"Coffee Bar","item":"Coffee (Hot)","healthy":false,"showCategory":true},{"descr":"Breakfast - Breakfast Sandwiches","category":"Breakfast","item":"Breakfast Sandwiches","healthy":false,"showCategory":false},{"descr":"Breakfast - Breakfast Menu","category":"Breakfast","item":"Breakfast Menu","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Misc - Wings","category":"Misc","item":"Wings","healthy":false,"showCategory":false},{"descr":"Misc - Fries","category":"Misc","item":"Fries","healthy":false,"showCategory":false}],"announcements":[{"id":199,"title":"McCormick's is closed for the 2021 season after Friday, September 24th.","announceType":"EATERY","startTimestamp":1632283200,"stopTimestamp":1640408340}],"icon":"coffee-brown"},{"id":3,"slug":"North-Star","name":"North Star Dining Room","nameshort":"North Star","about":"
\r\nThe North Star Dining Room<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. Enjoy an open kitchen concept and numerous unique food stations serving a huge variety of ethnic and regional American cuisine. Note: Our mid-afternoon Lite Lunch hours feature a limited menu.\r\n

\r\nMore Info:
North Star Dining Room<\/strong><\/a>\r\n

\r\n
\"North<\/a>\r\n

","aboutshort":"Dining room located in Appel Commons on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ecbhqf3ibeei09dds91viod5g8@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-2992","contactEmail":null,"serviceUnitId":7,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453527,"longitude":-76.475944,"location":"Appel Commons, Third floor","coordinates":{"latitude":42.453527,"longitude":-76.475944},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[{"id":216,"title":"North Star Dining Room is closed for renovation through the Spring 2022 semester!","announceType":"EATERY","startTimestamp":1639976400,"stopTimestamp":1654055940}],"icon":"restaurant-red"},{"id":20,"slug":"Okenshields","name":"Okenshields","nameshort":"Okenshields","about":"
\r\nOkenshields<\/strong> is a
dining room<\/a> for house residents, and for the entire Cornell community. With hundreds of menu options to choose from \u2013 from an Asian station to a healthy foods bar featuring whole grain salads and cut fruit \u2013 you're sure to find something that meets your fancy.\r\n

\r\nMore Info:
Okenshields<\/strong><\/a>\r\n

\r\n
\"Okenshields<\/a>\r\n

","aboutshort":"Dining room located in Willard Straight Hall on Central Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"3hku0mr66kapq1lh8fakug9kko@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6636","contactEmail":null,"serviceUnitId":10,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446491,"longitude":-76.485678,"location":"Willard Straight Hall","coordinates":{"latitude":42.446491,"longitude":-76.485678},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":4,"slug":"Risley-Dining","name":"Risley Dining Room","nameshort":"Risley Dining Room","about":"
\r\nRisley Dining Room<\/strong> is a 100% gluten-free, tree-nut-free, and peanut-free
dining room<\/a> for house residents, and for the entire Cornell community. There are always vegan and vegetarian choices. Modeled after the Christchurch Refectory at Oxford University, the dining room maintains the same Gothic charm as when it first opened as the all-Ivy \"Risley Great Hall\" in 1913. \r\n

\r\nMore Info:
Risley Dining Room<\/strong><\/a>\r\n

\r\n
\"Risley<\/a>\r\n

","aboutshort":"Dining room located in Risley Residential College on North Campus.","nutrition":"Risley Dining Room is certified 100% gluten-free, tree-nut-free, and peanut-free. Thank you for not bringing outside food into Risley!<\/strong>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"hq98btd396f3077p88d30c84fs@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-254-4229","contactEmail":null,"serviceUnitId":8,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.453117,"longitude":-76.481946,"location":"Risley Residential College","coordinates":{"latitude":42.453117,"longitude":-76.481946},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":5,"slug":"RPCC-Marketplace","name":"Robert Purcell Marketplace Eatery","nameshort":"RPCC Marketplace","about":"
\r\nThe Robert Purcell Marketplace Eatery<\/strong> is an award-winning
dining room<\/a> with a huge variety of menu options. Chef Kevin Moore, who has been with Cornell Dining for over 25 years, most recently at 104West!<\/a>, plans creative menus with roots in a variety of international traditions. Note: Our Late Dinner hours feature a limited menu.\r\n

\r\nMore Info:
Robert Purcell Marketplace Eatery<\/strong><\/a>\r\n

\r\n
\"Robert<\/a>\r\n

","aboutshort":"Dining room located in Robert Purcell Community Center on North Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"32uglqeiqfo9edhpp4tka8oqsc@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1138","contactEmail":null,"serviceUnitId":6,"campusArea":{"descr":"North Campus","descrshort":"North"},"latitude":42.455973,"longitude":-76.477354,"location":"RPCC, Third floor","coordinates":{"latitude":42.455973,"longitude":-76.477354},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640088000,"endTimestamp":1640100600,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":2,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Pork Breakfast Sausage","healthy":false,"sortIdx":3},{"item":"Home Fries","healthy":true,"sortIdx":4},{"item":"Pancakes with Syrup","healthy":false,"sortIdx":5},{"item":"French Toast Sticks","healthy":false,"sortIdx":6},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":7}]},{"category":"Grill Station","sortIdx":3,"items":[{"item":"Scrambled Tofu","healthy":true,"sortIdx":1},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":2}]},{"category":"Specialty Station","sortIdx":4,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":2},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":3},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":4},{"item":"Waffle Bar","healthy":false,"sortIdx":5},{"item":"Assorted Cereal","healthy":false,"sortIdx":6},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":7}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640100600,"endTimestamp":1640115000,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":5,"items":[{"item":"Beef Vegetable Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":6,"items":[{"item":"Salad Bar","healthy":true,"sortIdx":1},{"item":"Grains For Brains","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":7,"items":[{"item":"Pasta Primavera","healthy":false,"sortIdx":1},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":8,"items":[{"item":"Potato Tots","healthy":false,"sortIdx":1},{"item":"Calabacitas","healthy":true,"sortIdx":2},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":9,"items":[{"item":"Hacienda Cheese Quesadilla","healthy":false,"sortIdx":1},{"item":"Grilled Cheese Sandwich","healthy":false,"sortIdx":2}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640124000,"endTimestamp":1640133000,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":10,"items":[{"item":"Cornell Chicken Noodle Soup","healthy":false,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":11,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2},{"item":"Grains For Brains","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":12,"items":[{"item":"Salmon","healthy":false,"sortIdx":1},{"item":"Spanish Style Brown Rice & Beans","healthy":true,"sortIdx":2},{"item":"Honey Soy Baked Chicken with Peppers","healthy":true,"sortIdx":3}]},{"category":"Hot Traditional Station - Sides","sortIdx":13,"items":[{"item":"Steamed Vegetable Melange","healthy":true,"sortIdx":1},{"item":"Sauteed Broccoli","healthy":true,"sortIdx":2},{"item":"Sauteed Zucchini","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4},{"item":"Fried Potato Puffs","healthy":false,"sortIdx":5}]},{"category":"Grill Station","sortIdx":14,"items":[{"item":"BBQ Chicken Pizza","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-22","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640174400,"endTimestamp":1640187000,"start":"7:00am","end":"10:30am","menu":[{"category":"Breakfast Station - Hot","sortIdx":15,"items":[{"item":"Scrambled Eggs","healthy":true,"sortIdx":1},{"item":"Hard Boiled Eggs","healthy":true,"sortIdx":2},{"item":"Turkey Breakfast Sausage","healthy":true,"sortIdx":3},{"item":"Bacon","healthy":false,"sortIdx":4},{"item":"Steamed Brown Rice","healthy":true,"sortIdx":5},{"item":"Home Fries","healthy":true,"sortIdx":6}]},{"category":"Grill Station","sortIdx":16,"items":[{"item":"Bacon & Cheese Omelet","healthy":false,"sortIdx":1},{"item":"Steamed Fresh Vegetables","healthy":true,"sortIdx":2},{"item":"Western Scrambled Tofu","healthy":true,"sortIdx":3},{"item":"Build Your Own Omelette","healthy":true,"sortIdx":4},{"item":"Cheese Omelet","healthy":false,"sortIdx":5}]},{"category":"Specialty Station","sortIdx":17,"items":[{"item":"Fresh Whole Fruit","healthy":true,"sortIdx":1},{"item":"Fruit & Yogurt Bar","healthy":true,"sortIdx":2},{"item":"Oatmeal with Brown Sugar & Raisins","healthy":true,"sortIdx":3},{"item":"Waffle Bar","healthy":false,"sortIdx":4},{"item":"Assorted Cereal","healthy":false,"sortIdx":5},{"item":"Bagels & Baked Goods","healthy":false,"sortIdx":6}]}],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640187000,"endTimestamp":1640201400,"start":"10:30am","end":"2:30pm","menu":[{"category":"Soup Station","sortIdx":18,"items":[{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":1}]},{"category":"Salad Bar Station","sortIdx":19,"items":[{"item":"Fresh Fruit Slices","healthy":true,"sortIdx":1},{"item":"Salad Bar","healthy":true,"sortIdx":2},{"item":"House Made Dressings","healthy":false,"sortIdx":3}]},{"category":"Hot Traditional Station - Entrees","sortIdx":20,"items":[{"item":"Pasta with Sun-dried Tomato Basil & Feta","healthy":true,"sortIdx":1},{"item":"Kale Pesto Chicken","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Sides","sortIdx":21,"items":[{"item":"Chef's Choice Seasonal Vegetable","healthy":true,"sortIdx":1},{"item":"Seared Kale","healthy":true,"sortIdx":2},{"item":"Rosemary Roasted Red Skin Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":22,"items":[{"item":"Grilled Chicken Breast","healthy":true,"sortIdx":1},{"item":"Grilled Hot Dogs","healthy":false,"sortIdx":2},{"item":"Sauteed Vegetables","healthy":true,"sortIdx":3},{"item":"French Fries","healthy":false,"sortIdx":4}]},{"category":"Pizza Station","sortIdx":23,"items":[{"item":"Supreme Vegetable Pizza","healthy":false,"sortIdx":1},{"item":"White Garlic Hawaiian Pizza","healthy":false,"sortIdx":2}]},{"category":"Wok\/Asian Station","sortIdx":24,"items":[{"item":"Moo Goo Gai Pan","healthy":false,"sortIdx":1},{"item":"Vegetable Fried Rice","healthy":false,"sortIdx":2},{"item":"Long Grain Brown Rice","healthy":true,"sortIdx":3},{"item":"Steamed Jasmine Rice","healthy":false,"sortIdx":4}]}],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640210400,"endTimestamp":1640219400,"start":"5:00pm","end":"7:30pm","menu":[{"category":"Soup Station","sortIdx":25,"items":[{"item":"Cornell Cream of Mushroom Soup","healthy":false,"sortIdx":1},{"item":"Cornell Beef & Barley Mushroom Soup","healthy":true,"sortIdx":2}]},{"category":"Salad Bar Station","sortIdx":26,"items":[{"item":"Grains For Brains","healthy":true,"sortIdx":1},{"item":"Healthy Style Salad Station","healthy":true,"sortIdx":2}]},{"category":"Hot Traditional Station - Entrees","sortIdx":27,"items":[{"item":"Roasted Eggplant & Zucchini Casserole","healthy":false,"sortIdx":1},{"item":"Chef's Choice Vegan Entree","healthy":false,"sortIdx":2},{"item":"Chef's Choice Meat Entree","healthy":false,"sortIdx":3},{"item":"Roasted Pork Loin with Mango Mojo","healthy":true,"sortIdx":4},{"item":"Gourmet Pretzel Bar","healthy":false,"sortIdx":5}]},{"category":"Hot Traditional Station - Sides","sortIdx":28,"items":[{"item":"Steamed Winter Vegetables","healthy":true,"sortIdx":1},{"item":"Sauteed Super Greens","healthy":true,"sortIdx":2},{"item":"Roasted Garlic & Herb Potatoes","healthy":true,"sortIdx":3}]},{"category":"Grill Station","sortIdx":29,"items":[{"item":"Veggie Burger","healthy":true,"sortIdx":1},{"item":"Char-Grilled Hamburgers","healthy":false,"sortIdx":2},{"item":"Build Your Own Nachos","healthy":false,"sortIdx":3}]},{"category":"Pasta and Pizza Station","sortIdx":30,"items":[{"item":"Pasta By Design","healthy":false,"sortIdx":1},{"item":"Cheese Pizza","healthy":false,"sortIdx":2},{"item":"Broccoli Alfredo Pizza","healthy":false,"sortIdx":3},{"item":"Buffalo Chicken Pizza","healthy":false,"sortIdx":4}]},{"category":"Wok\/Asian Station","sortIdx":31,"items":[{"item":"Pancake Bar with Fruit Toppings & Syrups","healthy":false,"sortIdx":1},{"item":"Omelet Bar","healthy":false,"sortIdx":2}]}],"calSummary":"Dinner"}]},{"date":"2021-12-23","status":"EVENTS","events":[{"descr":"Breakfast","startTimestamp":1640260800,"endTimestamp":1640273400,"start":"7:00am","end":"10:30am","menu":[],"calSummary":"Breakfast"},{"descr":"Lunch","startTimestamp":1640273400,"endTimestamp":1640287800,"start":"10:30am","end":"2:30pm","menu":[],"calSummary":"Lunch"},{"descr":"Dinner","startTimestamp":1640296800,"endTimestamp":1640305800,"start":"5:00pm","end":"7:30pm","menu":[],"calSummary":"Dinner"}]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":30,"slug":"Rose-House-Dining","name":"Rose House Dining Room","nameshort":"Rose House Dining","about":"
\r\nThe Rose House Dining Room<\/strong> is a
dning room<\/a> for house residents, and for the entire Cornell community. This eatery features traditional hot entrees, an Asian station, a grill, a deli, and a salad bar. Continental breakfast on Saturday and Sunday. Sample Chef Matt Seeber's daily menu options \u2013 you won't be disappointed!\r\n

\r\nMore Info:
Rose House Dining Room<\/strong><\/a>\r\n

\r\n
\"Rose<\/a>\r\n

","aboutshort":"Dining room located in Flora Rose House on West Campus.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"mo4mqfpe88ucqaer728ovfei18@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-0337","contactEmail":null,"serviceUnitId":4,"campusArea":{"descr":"West Campus","descrshort":"West"},"latitude":42.447813,"longitude":-76.488791,"location":"Flora Rose House","coordinates":{"latitude":42.447813,"longitude":-76.488791},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Dining Room","descrshort":"dining room"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Meal Plan - Meal Swipe","descrshort":"Meal Plan - Swipe"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"restaurant-red"},{"id":21,"slug":"Rustys","name":"Rusty's","nameshort":"Rusty's","about":"
\r\nConveniently located in the lobby of Uris Hall, Rusty's<\/strong> offers Starbucks specialty coffees, Straight from the Oven baked goods, Freshtake Grab-n-Go sandwiches, salads and more. At any time of day, you're sure to find something to whet your appetite!\r\n

\r\nMore Info:
Rusty's<\/strong><\/a>\r\n

\r\n
\"Rusty's<\/a>\r\n

","aboutshort":"A great place to grab a quick coffee or a bite to eat on your way to class or work.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"sqp9nd9rt727fm7v2sgmfelkps@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-6656","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447399,"longitude":-76.482302,"location":"Uris Hall","coordinates":{"latitude":42.447399,"longitude":-76.482302},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Coffee Shop","descrshort":"coffee shop"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[{"descr":"Coffee Bar - Starbucks Coffee","category":"Coffee Bar","item":"Starbucks Coffee","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Tazo Tea","category":"Coffee Bar","item":"Tazo Tea","healthy":false,"showCategory":false},{"descr":"Coffee Bar - Hot Cocoa","category":"Coffee Bar","item":"Hot Cocoa","healthy":false,"showCategory":false},{"descr":"Beverages - Pepsi Beverages","category":"Beverages","item":"Pepsi Beverages","healthy":false,"showCategory":false},{"descr":"Bakery - Baked Goods","category":"Bakery","item":"Baked Goods","healthy":false,"showCategory":false},{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false}],"announcements":[],"icon":"coffee-brown"},{"id":13,"slug":"StraightMarket","name":"Straight from the Market","nameshort":"Straight from the Market","about":"With a farm-fresh marketplace flair, Straight from the Market offers a wide variety of fresh and marinated vegetables, hummus and tapenade bar, salad fixings, and Cornell Dairy ice cream on the main floor of Willard Straight Hall adjacent to the Straight Terrace. Healthy, flavorful, and ready-to-go meat, vegan, and vegetarian choices daily.","aboutshort":"A farm-fresh marketplace on the main floor of the Straight.","nutrition":null,"cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"ju94n6trv0ccoqcnd5u7otle50@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-3963","contactEmail":null,"serviceUnitId":11,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.446372,"longitude":-76.485786,"location":"Willard Straight Hall","coordinates":{"latitude":42.446372,"longitude":-76.485786},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Cafe","descrshort":"cafe"}],"diningCuisines":[],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"},{"descr":"Cash","descrshort":"Cash"}],"diningItems":[{"descr":"Cooled Items - Grab-n-Go","category":"Cooled Items","item":"Grab-n-Go","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Soup","category":"Soup & Salad","item":"Soup","healthy":false,"showCategory":false},{"descr":"Soup & Salad - Salads","category":"Soup & Salad","item":"Salads","healthy":false,"showCategory":false},{"descr":"Bakery - Snack Foods","category":"Bakery","item":"Snack Foods","healthy":false,"showCategory":false},{"descr":"Cornell Dairy - Ice Cream","category":"Cornell Dairy","item":"Ice Cream","healthy":false,"showCategory":true}],"announcements":[],"icon":"fastfood-blue"},{"id":23,"slug":"Trillium","name":"Trillium","nameshort":"Trillium","about":"
\r\nAt Trillium<\/strong> choose from a wide variety of food stations serving Mexican food, Asian dishes, sumptuous burgers, pasta bar, sandwiches, salads, soups, and made-to-order omelets, among many other delectable menu options.\r\n

\r\nMore Info:
Trillium<\/strong><\/a>\r\n

\r\n
\"Trillium<\/a>\r\n

","aboutshort":"Located in Kennedy Hall in the heart of Central Campus, Trillium is one of our most popular food courts.","nutrition":"
Nutrition Details<\/a>","cornellDining":true,"opHoursCalc":"AUTO-GOOGLE","opHoursCalcDescr":"Google Calendar - 7 Day Accuracy","opHoursDescr":null,"googleCalendarId":"i8v43jd76mugc62voucp4dqn9s@group.calendar.google.com","onlineOrdering":false,"onlineOrderUrl":null,"contactPhone":"607-255-1879","contactEmail":null,"serviceUnitId":null,"campusArea":{"descr":"Central Campus","descrshort":"Central"},"latitude":42.447883,"longitude":-76.479127,"location":"Kennedy Hall","coordinates":{"latitude":42.447883,"longitude":-76.479127},"operatingHours":[{"date":"2021-12-21","status":"EVENTS","events":[]},{"date":"2021-12-22","status":"EVENTS","events":[]},{"date":"2021-12-23","status":"EVENTS","events":[]},{"date":"2021-12-24","status":"EVENTS","events":[]},{"date":"2021-12-25","status":"EVENTS","events":[]},{"date":"2021-12-26","status":"EVENTS","events":[]},{"date":"2021-12-27","status":"EVENTS","events":[]},{"date":"2021-12-28","status":"EVENTS","events":[]}],"eateryTypes":[{"descr":"Food Court","descrshort":"food court"}],"diningCuisines":[{"name":"Mexican","nameshort":"Mexican","descr":""},{"name":"Asian","nameshort":"Asian","descr":""},{"name":"Pizza","nameshort":"Pizza","descr":""},{"name":"Italian","nameshort":"Italian","descr":null}],"payMethods":[{"descr":"Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)","descrshort":"Meal Plan - Debit"},{"descr":"Cornell Card","descrshort":"Cornell Card"},{"descr":"Major Credit Cards (VISA, MasterCard, AMEX)","descrshort":"Major Credit Cards"},{"descr":"Mobile Payments (Apple Pay, Google Wallet)","descrshort":"Mobile Payments"}],"diningItems":[],"announcements":[],"icon":"fastfood-blue"}]},"message":null,"meta":{"copyright":"Cornell University, Cornell Dining","responseDttm":"2021-12-22T11:15:18-0500"}} \ No newline at end of file From efb96950a7a0f8068680efd59ea6f8f28f0135f1 Mon Sep 17 00:00:00 2001 From: Daniel Weiner Date: Tue, 26 Mar 2024 20:31:26 -0400 Subject: [PATCH 231/305] Add users model --- src/user/serializers.py | 7 +++++++ src/user/urls.py | 10 ++++++++++ src/user/views.py | 8 ++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/user/serializers.py b/src/user/serializers.py index e69de29..993d20d 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from user.models import User + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['id', 'netid', 'name', 'email', 'favorite items', 'favorite eateries', 'created'] \ No newline at end of file diff --git a/src/user/urls.py b/src/user/urls.py index e69de29..5125932 100644 --- a/src/user/urls.py +++ b/src/user/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from user.views import UserViewSet +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register("", UserViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] \ No newline at end of file diff --git a/src/user/views.py b/src/user/views.py index 91ea44a..6d7ec08 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -1,3 +1,7 @@ -from django.shortcuts import render +from rest_framework import viewsets +from user.models import User +from user.serializers import UserSerializer -# Create your views here. +class UserViewSet: + queryset = User.objects.all() + serializer_class = UserSerializer \ No newline at end of file From 238bd487086b8e7f8c5b14e53179a067df4e3dbb Mon Sep 17 00:00:00 2001 From: Daniel Weiner Date: Tue, 26 Mar 2024 20:31:26 -0400 Subject: [PATCH 232/305] Add users model --- src/user/serializers.py | 7 +++++++ src/user/urls.py | 10 ++++++++++ src/user/views.py | 8 ++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/user/serializers.py b/src/user/serializers.py index e69de29..993d20d 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from user.models import User + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['id', 'netid', 'name', 'email', 'favorite items', 'favorite eateries', 'created'] \ No newline at end of file diff --git a/src/user/urls.py b/src/user/urls.py index e69de29..5125932 100644 --- a/src/user/urls.py +++ b/src/user/urls.py @@ -0,0 +1,10 @@ +from django.urls import path, include +from user.views import UserViewSet +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register("", UserViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] \ No newline at end of file diff --git a/src/user/views.py b/src/user/views.py index 91ea44a..6d7ec08 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -1,3 +1,7 @@ -from django.shortcuts import render +from rest_framework import viewsets +from user.models import User +from user.serializers import UserSerializer -# Create your views here. +class UserViewSet: + queryset = User.objects.all() + serializer_class = UserSerializer \ No newline at end of file From d340115133627d339e572788658b00fde160042b Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 27 Mar 2024 18:22:26 -0400 Subject: [PATCH 233/305] Add prints for by day --- src/eatery/serializers.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 25883a1..8b3debc 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -57,6 +57,11 @@ def get_events(self, obj): day = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=day_offset) day_unix = int(day.timestamp()) day_end_unix = int((day + timedelta(days=1)).timestamp()) + print(f"Now: {now}") + print(f"Day: {day}") + print(f"Day Unix: {day_unix}") + print(f"Day End Unix: {day_end_unix}") + print() events = Event.objects.filter(eatery=obj.id, start__gte=day_unix, start__lt=day_end_unix) serializer = EventSerializerOptimized(instance=events, many=True) return serializer.data @@ -64,4 +69,3 @@ def get_events(self, obj): class Meta: model = Eatery fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] - From 52b6cdeb98e4b67205500130f586cb99d88a2eb3 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 27 Mar 2024 18:26:46 -0400 Subject: [PATCH 234/305] Revert "Add prints for by day" This reverts commit d340115133627d339e572788658b00fde160042b. --- src/eatery/serializers.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 8b3debc..25883a1 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -57,11 +57,6 @@ def get_events(self, obj): day = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=day_offset) day_unix = int(day.timestamp()) day_end_unix = int((day + timedelta(days=1)).timestamp()) - print(f"Now: {now}") - print(f"Day: {day}") - print(f"Day Unix: {day_unix}") - print(f"Day End Unix: {day_end_unix}") - print() events = Event.objects.filter(eatery=obj.id, start__gte=day_unix, start__lt=day_end_unix) serializer = EventSerializerOptimized(instance=events, many=True) return serializer.data @@ -69,3 +64,4 @@ def get_events(self, obj): class Meta: model = Eatery fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + From ce571c156be685c11f907d8a92749073b54a261b Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 27 Mar 2024 18:29:09 -0400 Subject: [PATCH 235/305] Add prints for by day --- src/eatery/serializers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index 25883a1..dbe4c0d 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -57,6 +57,10 @@ def get_events(self, obj): day = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=day_offset) day_unix = int(day.timestamp()) day_end_unix = int((day + timedelta(days=1)).timestamp()) + print(f"Now: {now}") + print(f"Day: {day}") + print(f"Day Unix: {day_unix}") + print(f"Day End Unix: {day_end_unix}") events = Event.objects.filter(eatery=obj.id, start__gte=day_unix, start__lt=day_end_unix) serializer = EventSerializerOptimized(instance=events, many=True) return serializer.data From 12e24a1d1e22bd41fa23d27ec714d567a8807487 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 2 Apr 2024 16:00:32 -0400 Subject: [PATCH 236/305] Adding firebase --- .envrctemplate | 12 ------------ .gitignore | 1 + requirements.txt | 25 ++++++++++++++++++++----- src/eatery_blue_backend/settings.py | 1 + src/eatery_blue_backend/urls.py | 1 + src/user/auth.py | 17 +++++++++++++++++ src/user/migrations/0001_initial.py | 29 +++++++++++++++++++++++++++++ src/user/models.py | 5 +++-- src/user/serializers.py | 12 +++++++++++- src/user/views.py | 11 +++++++++-- 10 files changed, 92 insertions(+), 22 deletions(-) delete mode 100644 .envrctemplate create mode 100644 src/user/auth.py create mode 100644 src/user/migrations/0001_initial.py diff --git a/.envrctemplate b/.envrctemplate deleted file mode 100644 index f5d34f9..0000000 --- a/.envrctemplate +++ /dev/null @@ -1,12 +0,0 @@ -export DJANGO_ALLOWED_HOSTS= -export IS_PROD= -export POSTGRES_NAME= -export POSTGRES_USER= -export POSTGRES_PASSWORD= -export POSTGRES_HOST= -export POSTGRES_PORT= -export DJANGO_SECRET_KEY= - -# Not used -export CORNELL_VENDOR_TOKEN= -export CORNELL_VENDOR_API_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 749e7f7..e33b07e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .envrc .envlocal .envremote +secrets/* # Hardcoded stuff db_snapshots/ diff --git a/requirements.txt b/requirements.txt index d86038e..e820e32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,27 +1,42 @@ asgiref==3.4.1 black==21.12b0 +CacheControl==0.14.0 cachetools==4.2.4 certifi==2021.10.8 +cffi==1.16.0 charset-normalizer==2.0.9 click==8.0.3 +cryptography==42.0.5 Django==4.0 djangorestframework==3.13.1 -google-api-core==2.3.2 +firebase-admin==6.5.0 +google-api-core==2.18.0 google-api-python-client==2.33.0 -google-auth==2.3.3 +google-auth==2.29.0 google-auth-httplib2==0.1.0 google-auth-oauthlib==0.4.6 -googleapis-common-protos==1.54.0 +google-cloud-core==2.4.1 +google-cloud-firestore==2.15.0 +google-cloud-storage==2.16.0 +google-crc32c==1.5.0 +google-resumable-media==2.7.0 +googleapis-common-protos==1.63.0 +grpcio==1.62.1 +grpcio-status==1.62.1 httplib2==0.20.2 idna==3.3 +msgpack==1.0.8 mypy-extensions==0.4.3 oauthlib==3.1.1 pathspec==0.9.0 platformdirs==2.4.0 -protobuf==3.19.1 +proto-plus==1.23.0 +protobuf==4.25.3 psycopg2-binary==2.9.3 pyasn1==0.4.8 pyasn1-modules==0.2.8 +pycparser==2.22 +PyJWT==2.8.0 pyparsing==3.0.6 pytz==2021.3 requests==2.26.0 @@ -32,4 +47,4 @@ sqlparse==0.4.2 tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 -urllib3==1.26.7 +urllib3==1.26.7 \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 75fe88e..2222601 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -47,6 +47,7 @@ "report", "item", "category", + "user", # Third party "rest_framework", ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 154f541..0286eea 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -8,4 +8,5 @@ path("item/", include("item.urls")), path("category/", include("category.urls")), path("report/", include("report.urls")), + path("users/", include("user.urls")), ] diff --git a/src/user/auth.py b/src/user/auth.py new file mode 100644 index 0000000..23dcf4b --- /dev/null +++ b/src/user/auth.py @@ -0,0 +1,17 @@ +# Import the Firebase service +import firebase_admin +from firebase_admin import auth +from firebase_admin import credentials +import os + +cred = credentials.Certificate(os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")) +default_app = firebase_admin.initialize_app(cred) + + +# Initialize the default app +# default_app = firebase_admin.initialize_app() +print(default_app.name) # "[DEFAULT]" + +uid = "DTkATsJbRJd5VO3E82V7M8HbTS83" +user = auth.get_user(uid) +print(user.email) diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py new file mode 100644 index 0000000..45e7ce5 --- /dev/null +++ b/src/user/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 4.0 on 2024-03-27 18:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('eatery', '0005_alter_eatery_campus_area'), + ('item', '0002_alter_item_id'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('netid', models.CharField(default='User', max_length=40)), + ('token', models.CharField(default='User', max_length=40)), + ('name', models.CharField(default='User', max_length=40)), + ('is_admin', models.BooleanField(default=False)), + ('last_active', models.DateTimeField(auto_now=True)), + ('favorite_eateries', models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery')), + ('favorite_items', models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item')), + ], + ), + ] diff --git a/src/user/models.py b/src/user/models.py index ce9503c..bda4510 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -1,10 +1,11 @@ from django.db import models -# Create your models here. class User(models.Model): + userId = models.AutoField(primary_key=True) + token = models.CharField(max_length=40) + refresh_token = models.CharField(max_length=40) netid = models.CharField(max_length=40, default="User") - token = models.CharField(max_length=40, default="User") name = models.CharField(max_length=40, default="User") favorite_items = models.ManyToManyField( "item.Item", related_name="favorited_by", blank=True diff --git a/src/user/serializers.py b/src/user/serializers.py index 993d20d..d40f3a8 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -1,7 +1,17 @@ from rest_framework import serializers from user.models import User + class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ['id', 'netid', 'name', 'email', 'favorite items', 'favorite eateries', 'created'] \ No newline at end of file + fields = [ + "id", + "netid", + "token", + "name", + "favorite_items", + "favorite_eateries", + "is_admin", + "last_active", + ] diff --git a/src/user/views.py b/src/user/views.py index 6d7ec08..4afff42 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -1,7 +1,14 @@ from rest_framework import viewsets from user.models import User from user.serializers import UserSerializer +from rest_framework.views import APIView -class UserViewSet: + +class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() - serializer_class = UserSerializer \ No newline at end of file + serializer_class = UserSerializer + + +class SSOViewSet(APIView): + def post(self, request): + pass From 503f4be0e6ad34b12aa6c743dd7a5f4d2d4977bd Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 2 Apr 2024 16:00:32 -0400 Subject: [PATCH 237/305] Adding firebase --- .envrctemplate | 12 ------------ .gitignore | 1 + requirements.txt | 25 ++++++++++++++++++++----- src/eatery_blue_backend/settings.py | 1 + src/eatery_blue_backend/urls.py | 1 + src/user/auth.py | 17 +++++++++++++++++ src/user/migrations/0001_initial.py | 29 +++++++++++++++++++++++++++++ src/user/models.py | 5 +++-- src/user/serializers.py | 12 +++++++++++- src/user/views.py | 11 +++++++++-- 10 files changed, 92 insertions(+), 22 deletions(-) delete mode 100644 .envrctemplate create mode 100644 src/user/auth.py create mode 100644 src/user/migrations/0001_initial.py diff --git a/.envrctemplate b/.envrctemplate deleted file mode 100644 index f5d34f9..0000000 --- a/.envrctemplate +++ /dev/null @@ -1,12 +0,0 @@ -export DJANGO_ALLOWED_HOSTS= -export IS_PROD= -export POSTGRES_NAME= -export POSTGRES_USER= -export POSTGRES_PASSWORD= -export POSTGRES_HOST= -export POSTGRES_PORT= -export DJANGO_SECRET_KEY= - -# Not used -export CORNELL_VENDOR_TOKEN= -export CORNELL_VENDOR_API_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 749e7f7..e33b07e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .envrc .envlocal .envremote +secrets/* # Hardcoded stuff db_snapshots/ diff --git a/requirements.txt b/requirements.txt index d86038e..e820e32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,27 +1,42 @@ asgiref==3.4.1 black==21.12b0 +CacheControl==0.14.0 cachetools==4.2.4 certifi==2021.10.8 +cffi==1.16.0 charset-normalizer==2.0.9 click==8.0.3 +cryptography==42.0.5 Django==4.0 djangorestframework==3.13.1 -google-api-core==2.3.2 +firebase-admin==6.5.0 +google-api-core==2.18.0 google-api-python-client==2.33.0 -google-auth==2.3.3 +google-auth==2.29.0 google-auth-httplib2==0.1.0 google-auth-oauthlib==0.4.6 -googleapis-common-protos==1.54.0 +google-cloud-core==2.4.1 +google-cloud-firestore==2.15.0 +google-cloud-storage==2.16.0 +google-crc32c==1.5.0 +google-resumable-media==2.7.0 +googleapis-common-protos==1.63.0 +grpcio==1.62.1 +grpcio-status==1.62.1 httplib2==0.20.2 idna==3.3 +msgpack==1.0.8 mypy-extensions==0.4.3 oauthlib==3.1.1 pathspec==0.9.0 platformdirs==2.4.0 -protobuf==3.19.1 +proto-plus==1.23.0 +protobuf==4.25.3 psycopg2-binary==2.9.3 pyasn1==0.4.8 pyasn1-modules==0.2.8 +pycparser==2.22 +PyJWT==2.8.0 pyparsing==3.0.6 pytz==2021.3 requests==2.26.0 @@ -32,4 +47,4 @@ sqlparse==0.4.2 tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 -urllib3==1.26.7 +urllib3==1.26.7 \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 75fe88e..2222601 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -47,6 +47,7 @@ "report", "item", "category", + "user", # Third party "rest_framework", ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 154f541..0286eea 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -8,4 +8,5 @@ path("item/", include("item.urls")), path("category/", include("category.urls")), path("report/", include("report.urls")), + path("users/", include("user.urls")), ] diff --git a/src/user/auth.py b/src/user/auth.py new file mode 100644 index 0000000..23dcf4b --- /dev/null +++ b/src/user/auth.py @@ -0,0 +1,17 @@ +# Import the Firebase service +import firebase_admin +from firebase_admin import auth +from firebase_admin import credentials +import os + +cred = credentials.Certificate(os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")) +default_app = firebase_admin.initialize_app(cred) + + +# Initialize the default app +# default_app = firebase_admin.initialize_app() +print(default_app.name) # "[DEFAULT]" + +uid = "DTkATsJbRJd5VO3E82V7M8HbTS83" +user = auth.get_user(uid) +print(user.email) diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py new file mode 100644 index 0000000..45e7ce5 --- /dev/null +++ b/src/user/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 4.0 on 2024-03-27 18:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('eatery', '0005_alter_eatery_campus_area'), + ('item', '0002_alter_item_id'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('netid', models.CharField(default='User', max_length=40)), + ('token', models.CharField(default='User', max_length=40)), + ('name', models.CharField(default='User', max_length=40)), + ('is_admin', models.BooleanField(default=False)), + ('last_active', models.DateTimeField(auto_now=True)), + ('favorite_eateries', models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery')), + ('favorite_items', models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item')), + ], + ), + ] diff --git a/src/user/models.py b/src/user/models.py index ce9503c..bda4510 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -1,10 +1,11 @@ from django.db import models -# Create your models here. class User(models.Model): + userId = models.AutoField(primary_key=True) + token = models.CharField(max_length=40) + refresh_token = models.CharField(max_length=40) netid = models.CharField(max_length=40, default="User") - token = models.CharField(max_length=40, default="User") name = models.CharField(max_length=40, default="User") favorite_items = models.ManyToManyField( "item.Item", related_name="favorited_by", blank=True diff --git a/src/user/serializers.py b/src/user/serializers.py index 993d20d..d40f3a8 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -1,7 +1,17 @@ from rest_framework import serializers from user.models import User + class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ['id', 'netid', 'name', 'email', 'favorite items', 'favorite eateries', 'created'] \ No newline at end of file + fields = [ + "id", + "netid", + "token", + "name", + "favorite_items", + "favorite_eateries", + "is_admin", + "last_active", + ] diff --git a/src/user/views.py b/src/user/views.py index 6d7ec08..4afff42 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -1,7 +1,14 @@ from rest_framework import viewsets from user.models import User from user.serializers import UserSerializer +from rest_framework.views import APIView -class UserViewSet: + +class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() - serializer_class = UserSerializer \ No newline at end of file + serializer_class = UserSerializer + + +class SSOViewSet(APIView): + def post(self, request): + pass From 8fb2cdb37cea90a8331409d527cbc55c114c335e Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 2 Apr 2024 16:06:07 -0400 Subject: [PATCH 238/305] Fixing secrets --- .gitignore | 2 +- secrets/.envrctemplate | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 secrets/.envrctemplate diff --git a/.gitignore b/.gitignore index e33b07e..7c968fa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ .envrc .envlocal .envremote -secrets/* +secrets/eatery-blue-f810e-firebase-adminsdk-ds3co-7902015462.json # Hardcoded stuff db_snapshots/ diff --git a/secrets/.envrctemplate b/secrets/.envrctemplate new file mode 100644 index 0000000..1a0f3cf --- /dev/null +++ b/secrets/.envrctemplate @@ -0,0 +1,13 @@ +export DJANGO_ALLOWED_HOSTS= +export IS_PROD= +export POSTGRES_NAME= +export POSTGRES_USER= +export POSTGRES_PASSWORD= +export POSTGRES_HOST= +export POSTGRES_PORT= +export DJANGO_SECRET_KEY= +export GOOGLE_APPLICATION_CREDENTIALS= + +# Not used +export CORNELL_VENDOR_TOKEN= +export CORNELL_VENDOR_API_KEY= \ No newline at end of file From f5a2a62680cfd1ba98024fd77e7c356fd2f838f2 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 2 Apr 2024 16:06:07 -0400 Subject: [PATCH 239/305] Fixing secrets --- .gitignore | 2 +- secrets/.envrctemplate | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 secrets/.envrctemplate diff --git a/.gitignore b/.gitignore index e33b07e..7c968fa 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ .envrc .envlocal .envremote -secrets/* +secrets/eatery-blue-f810e-firebase-adminsdk-ds3co-7902015462.json # Hardcoded stuff db_snapshots/ diff --git a/secrets/.envrctemplate b/secrets/.envrctemplate new file mode 100644 index 0000000..1a0f3cf --- /dev/null +++ b/secrets/.envrctemplate @@ -0,0 +1,13 @@ +export DJANGO_ALLOWED_HOSTS= +export IS_PROD= +export POSTGRES_NAME= +export POSTGRES_USER= +export POSTGRES_PASSWORD= +export POSTGRES_HOST= +export POSTGRES_PORT= +export DJANGO_SECRET_KEY= +export GOOGLE_APPLICATION_CREDENTIALS= + +# Not used +export CORNELL_VENDOR_TOKEN= +export CORNELL_VENDOR_API_KEY= \ No newline at end of file From efc83a3bdae172531c899e8c20b29bca1a3b6036 Mon Sep 17 00:00:00 2001 From: Thomas Vignos <113550626+tjvignos@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:33:46 -0400 Subject: [PATCH 240/305] Update src/item/controllers/populate_item.py Co-authored-by: Archit Mehta <4architmehta@gmail.com> --- src/item/controllers/populate_item.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 78f048f..1c56b04 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -51,8 +51,8 @@ def process(self, categories_dict, json_eateries): json_eateries += json_obj["eateries"] for json_eatery in json_eateries: - if int(json_eatery["id"]) in categories_dict: - eatery_menus = categories_dict[int(json_eatery["id"])] + if (eatery_id := int(json_eatery["id"])) in categories_dict: + eatery_menus = categories_dict[eatery_id] else: continue From bc17672ef40481d21509192aea4e81aad64aa7aa Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Sun, 21 Apr 2024 09:18:27 -0400 Subject: [PATCH 241/305] Address comments --- src/category/controllers/populate_category.py | 9 +++++---- src/item/controllers/populate_item.py | 10 ++++++---- src/util/constants.py | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 src/util/constants.py diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index e4bede6..43601a6 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -2,6 +2,7 @@ from category.serializers import CategorySerializer from item.models import Item import json +from util.constants import eatery_is_cafe """ Add categories to Category Model from CornellDiningNow. @@ -78,9 +79,9 @@ def process(self, events_dict, json_eateries): categories_dict = {} - with open("./static_sources/external_eateries.json", "r") as file: - json_obj = json.load(file) - json_eateries += json_obj["eateries"] + with open("./static_sources/external_eateries.json", "r") as external_eateries_file: + external_eateries_json = json.load(external_eateries_file) + json_eateries.extend(external_eateries_json["eateries"]) for json_eatery in json_eateries: eatery_id = int(json_eatery["id"]) @@ -92,7 +93,7 @@ def process(self, events_dict, json_eateries): else: continue - is_cafe = not "Dining Room" in {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} + is_cafe = eatery_is_cafe(json_eatery) """ For every event in an eatery --> for every menu in an eatery --> get categories diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 1c56b04..52cb617 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -4,6 +4,7 @@ from eatery.serializers import EaterySerializer import string import json +from util.constants import eatery_is_cafe class PopulateItemController(): def __init__(self): @@ -13,6 +14,7 @@ def generate_cafe_items(self, menu, json_eatery): for json_item in json_eatery["diningItems"]: + print(json_item) category_name = json_item['category'].strip() category_id = menu[category_name] @@ -46,9 +48,9 @@ def generate_dining_hall_items(self, menu, json_event, json_eatery): print(item.errors) def process(self, categories_dict, json_eateries): - with open("./static_sources/external_eateries.json", "r") as file: - json_obj = json.load(file) - json_eateries += json_obj["eateries"] + with open("./static_sources/external_eateries.json", "r") as external_eateries_file: + external_eateries_json = json.load(external_eateries_file) + json_eateries.extend(external_eateries_json["eateries"]) for json_eatery in json_eateries: if (eatery_id := int(json_eatery["id"])) in categories_dict: @@ -59,7 +61,7 @@ def process(self, categories_dict, json_eateries): iter = list(eatery_menus.keys()) i = 0 - is_cafe = not "Dining Room" in {eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]} + is_cafe = eatery_is_cafe(json_eatery) json_dates = json_eatery["operatingHours"] for json_date in json_dates: diff --git a/src/util/constants.py b/src/util/constants.py new file mode 100644 index 0000000..c658cf0 --- /dev/null +++ b/src/util/constants.py @@ -0,0 +1,2 @@ +def eatery_is_cafe(json_eatery): + return not "Dining Room" in [eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]] \ No newline at end of file From 442795763bc74ea916447ed0899e0deebf774f93 Mon Sep 17 00:00:00 2001 From: Thomas Vignos Date: Wed, 24 Apr 2024 17:56:36 -0400 Subject: [PATCH 242/305] Fix KeyError --- src/item/controllers/populate_item.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 52cb617..132d775 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -13,10 +13,11 @@ def __init__(self): def generate_cafe_items(self, menu, json_eatery): for json_item in json_eatery["diningItems"]: - - print(json_item) category_name = json_item['category'].strip() - category_id = menu[category_name] + try: + category_id = menu[category_name] + except KeyError: + continue data = { "category" : category_id, From 3cfd2318ade880f156a8d9f94838376fdf8890a9 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 5 Sep 2024 12:09:31 -0400 Subject: [PATCH 243/305] finishing initial changes --- .pre-commit-config.yaml | 18 ++++++++++++++++++ requirements.txt | 11 +++++++++-- test.py | 3 +++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 test.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6e08eb5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: + # Using this mirror lets us use mypyc-compiled black, which is about 2x faster + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.8.0 + hooks: + - id: black + # It is recommended to specify the latest version of Python + # supported by your project here, or alternatively use + # pre-commit's default_language_version, see + # https://pre-commit.com/#top_level-default_language_version + language_version: python3.9 + # - repo: https://github.com/astral-sh/ruff-pre-commit + # # Ruff version. + # rev: v0.6.3 + # hooks: + # # Run the linter. + # - id: ruff + # args: [--fix] diff --git a/requirements.txt b/requirements.txt index d86038e..f0e1f1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,13 @@ asgiref==3.4.1 -black==21.12b0 cachetools==4.2.4 certifi==2021.10.8 +cfgv==3.4.0 charset-normalizer==2.0.9 click==8.0.3 +distlib==0.3.8 Django==4.0 djangorestframework==3.13.1 +filelock==3.15.4 google-api-core==2.3.2 google-api-python-client==2.33.0 google-auth==2.3.3 @@ -13,17 +15,21 @@ google-auth-httplib2==0.1.0 google-auth-oauthlib==0.4.6 googleapis-common-protos==1.54.0 httplib2==0.20.2 +identify==2.6.0 idna==3.3 mypy-extensions==0.4.3 +nodeenv==1.9.1 oauthlib==3.1.1 pathspec==0.9.0 -platformdirs==2.4.0 +platformdirs==4.2.2 +pre-commit==3.8.0 protobuf==3.19.1 psycopg2-binary==2.9.3 pyasn1==0.4.8 pyasn1-modules==0.2.8 pyparsing==3.0.6 pytz==2021.3 +PyYAML==6.0.2 requests==2.26.0 requests-oauthlib==1.3.0 rsa==4.8 @@ -33,3 +39,4 @@ tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 urllib3==1.26.7 +virtualenv==20.26.3 \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..3e905cf --- /dev/null +++ b/test.py @@ -0,0 +1,3 @@ +import os + +a = [1, 2, 3] From 8b886d6f0205324d2fcfc66f3b62e6ec01219387 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 5 Sep 2024 12:09:31 -0400 Subject: [PATCH 244/305] finishing initial changes --- .pre-commit-config.yaml | 18 ++++++++++++++++++ requirements.txt | 11 +++++++++-- test.py | 3 +++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 test.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6e08eb5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: + # Using this mirror lets us use mypyc-compiled black, which is about 2x faster + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.8.0 + hooks: + - id: black + # It is recommended to specify the latest version of Python + # supported by your project here, or alternatively use + # pre-commit's default_language_version, see + # https://pre-commit.com/#top_level-default_language_version + language_version: python3.9 + # - repo: https://github.com/astral-sh/ruff-pre-commit + # # Ruff version. + # rev: v0.6.3 + # hooks: + # # Run the linter. + # - id: ruff + # args: [--fix] diff --git a/requirements.txt b/requirements.txt index d86038e..f0e1f1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,13 @@ asgiref==3.4.1 -black==21.12b0 cachetools==4.2.4 certifi==2021.10.8 +cfgv==3.4.0 charset-normalizer==2.0.9 click==8.0.3 +distlib==0.3.8 Django==4.0 djangorestframework==3.13.1 +filelock==3.15.4 google-api-core==2.3.2 google-api-python-client==2.33.0 google-auth==2.3.3 @@ -13,17 +15,21 @@ google-auth-httplib2==0.1.0 google-auth-oauthlib==0.4.6 googleapis-common-protos==1.54.0 httplib2==0.20.2 +identify==2.6.0 idna==3.3 mypy-extensions==0.4.3 +nodeenv==1.9.1 oauthlib==3.1.1 pathspec==0.9.0 -platformdirs==2.4.0 +platformdirs==4.2.2 +pre-commit==3.8.0 protobuf==3.19.1 psycopg2-binary==2.9.3 pyasn1==0.4.8 pyasn1-modules==0.2.8 pyparsing==3.0.6 pytz==2021.3 +PyYAML==6.0.2 requests==2.26.0 requests-oauthlib==1.3.0 rsa==4.8 @@ -33,3 +39,4 @@ tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 urllib3==1.26.7 +virtualenv==20.26.3 \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..3e905cf --- /dev/null +++ b/test.py @@ -0,0 +1,3 @@ +import os + +a = [1, 2, 3] From 6c79ff5b665ec25925167a4cc7a0df230c9fa34e Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 5 Sep 2024 12:10:35 -0400 Subject: [PATCH 245/305] Running black on all files --- src/category/apps.py | 4 +- src/category/controllers/populate_category.py | 24 ++- src/category/migrations/0001_initial.py | 17 ++- .../migrations/0002_alter_category_id.py | 10 +- src/category/models.py | 2 +- src/category/serializers.py | 1 + src/category/urls.py | 2 +- src/category/views.py | 3 +- src/eatery/controllers/populate_eatery.py | 3 +- src/eatery/controllers/update_eatery.py | 1 + src/eatery/datatype/Eatery.py | 8 +- src/eatery/datatype/EateryAlert.py | 12 +- src/eatery/migrations/0001_initial.py | 46 ++++-- src/eatery/migrations/0002_alter_eatery_id.py | 10 +- .../migrations/0003_alter_eatery_image_url.py | 11 +- .../0004_alter_eatery_campus_area.py | 20 ++- .../0005_alter_eatery_campus_area.py | 19 ++- src/eatery/models.py | 3 +- src/eatery/permissions.py | 9 +- src/eatery/serializers.py | 107 +++++++++++-- src/eatery/urls.py | 22 +-- src/eatery/util/constants.py | 15 +- src/eatery/util/convert_from_json.py | 2 +- src/eatery/util/time.py | 1 + src/eatery/views.py | 33 +++-- .../management/commands/populate_models.py | 140 ++++++++++-------- src/eatery_blue_backend/settings.py | 2 - src/event/apps.py | 4 +- src/event/controllers/populate_event.py | 69 +++++---- .../update_models/CornellDiningEvents.py | 8 +- .../schedule/CornellDiningEvents.py | 5 +- .../schedule/RepeatingSchedule.py | 6 +- src/event/datatype/Event.py | 5 +- src/event/datatype/Menu.py | 1 + src/event/datatype/MenuCategory.py | 4 +- src/event/datatype/MenuItem.py | 31 ++-- src/event/datatype/MenuItemSection.py | 5 +- src/event/datatype/MenuSubItem.py | 10 +- src/event/datatype/WaitTime.py | 14 +- src/event/datatype/WaitTimesDay.py | 13 +- src/event/migrations/0001_initial.py | 37 ++++- src/event/migrations/0002_alter_event_id.py | 10 +- .../0003_alter_event_event_description.py | 21 ++- .../0004_alter_event_event_description.py | 21 ++- src/event/models.py | 24 ++- src/event/serializers.py | 7 +- src/event/urls.py | 2 +- src/event/views.py | 3 +- src/item/apps.py | 4 +- src/item/controllers/populate_item.py | 43 +++--- src/item/migrations/0001_initial.py | 19 ++- src/item/migrations/0002_alter_item_id.py | 10 +- src/item/models.py | 9 +- src/item/serializers.py | 12 +- src/item/urls.py | 2 +- src/item/views.py | 3 +- src/manage.py | 3 +- src/person/admin.py | 2 +- src/person/apps.py | 4 +- src/person/migrations/0001_initial.py | 65 ++++++-- src/person/models.py | 21 ++- src/person/serializers.py | 6 +- src/person/urls.py | 2 +- src/person/views.py | 4 +- src/report/apps.py | 4 +- src/report/migrations/0001_initial.py | 28 +++- src/report/migrations/0002_report_netid.py | 6 +- src/report/models.py | 6 +- src/report/serializers.py | 2 +- src/report/urls.py | 2 +- src/util/constants.py | 4 +- 71 files changed, 713 insertions(+), 375 deletions(-) diff --git a/src/category/apps.py b/src/category/apps.py index 1f46433..e953ee6 100644 --- a/src/category/apps.py +++ b/src/category/apps.py @@ -2,5 +2,5 @@ class CategoryConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'category' + default_auto_field = "django.db.models.BigAutoField" + name = "category" diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index 43601a6..e8a23fd 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -21,10 +21,20 @@ def generate_dining_hall_categories(self, json_event, event): """ category_items = {} - category_order = ["Chef's Table", "Chef's Table - Sides", "Grill", "Wok", - "Wok/Asian Station", "Iron Grill", "Mexican Station", "Global", - "Halal", "Kosher Station", "Flat Top Grill"] - + category_order = [ + "Chef's Table", + "Chef's Table - Sides", + "Grill", + "Wok", + "Wok/Asian Station", + "Iron Grill", + "Mexican Station", + "Global", + "Halal", + "Kosher Station", + "Flat Top Grill", + ] + def sort_menu(menu): try: return category_order.index(menu["category"].strip()) @@ -79,7 +89,9 @@ def process(self, events_dict, json_eateries): categories_dict = {} - with open("./static_sources/external_eateries.json", "r") as external_eateries_file: + with open( + "./static_sources/external_eateries.json", "r" + ) as external_eateries_file: external_eateries_json = json.load(external_eateries_file) json_eateries.extend(external_eateries_json["eateries"]) @@ -94,7 +106,7 @@ def process(self, events_dict, json_eateries): continue is_cafe = eatery_is_cafe(json_eatery) - + """ For every event in an eatery --> for every menu in an eatery --> get categories """ diff --git a/src/category/migrations/0001_initial.py b/src/category/migrations/0001_initial.py index bdcbcb3..dca2274 100644 --- a/src/category/migrations/0001_initial.py +++ b/src/category/migrations/0001_initial.py @@ -9,16 +9,23 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('event', '0001_initial'), + ("event", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Category', + name="Category", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('category', models.CharField(default='General', max_length=40)), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='menu', to='event.event')), + ("id", models.AutoField(primary_key=True, serialize=False)), + ("category", models.CharField(default="General", max_length=40)), + ( + "event", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="menu", + to="event.event", + ), + ), ], ), ] diff --git a/src/category/migrations/0002_alter_category_id.py b/src/category/migrations/0002_alter_category_id.py index 410927c..cbb43d1 100644 --- a/src/category/migrations/0002_alter_category_id.py +++ b/src/category/migrations/0002_alter_category_id.py @@ -6,13 +6,15 @@ class Migration(migrations.Migration): dependencies = [ - ('category', '0001_initial'), + ("category", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='category', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + model_name="category", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), ), ] diff --git a/src/category/models.py b/src/category/models.py index 01b85aa..9538a42 100644 --- a/src/category/models.py +++ b/src/category/models.py @@ -5,6 +5,6 @@ class Category(models.Model): event = models.ForeignKey(Event, related_name="menu", on_delete=models.DO_NOTHING) category = models.CharField(max_length=40, default="General") - + def __str__(self): return self.category diff --git a/src/category/serializers.py b/src/category/serializers.py index 6af5027..74fc181 100644 --- a/src/category/serializers.py +++ b/src/category/serializers.py @@ -16,6 +16,7 @@ class Meta: model = Category fields = ["id", "category", "event", "items"] + class CategorySerializerOptimized(serializers.ModelSerializer): items = ItemSerializerOptimized(many=True, read_only=True) diff --git a/src/category/urls.py b/src/category/urls.py index d32462d..e245c26 100644 --- a/src/category/urls.py +++ b/src/category/urls.py @@ -7,4 +7,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/category/views.py b/src/category/views.py index a95917c..bda65d2 100644 --- a/src/category/views.py +++ b/src/category/views.py @@ -2,6 +2,7 @@ from category.models import Category from category.serializers import CategorySerializer + class CategoryViewSet(viewsets.ModelViewSet): queryset = Category.objects.all() - serializer_class = CategorySerializer \ No newline at end of file + serializer_class = CategorySerializer diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index f3f6634..3961d7d 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -4,6 +4,7 @@ from eatery.models import Eatery from django.core.exceptions import ObjectDoesNotExist + class PopulateEateryController: def __init__(self): self = self @@ -66,7 +67,7 @@ def add_eatery_store(self): for line in file: if len(line) > 2: json_objs.append(json.loads(line)) - + for json_obj in json_objs: try: object = Eatery.objects.get(id=int(json_obj["id"])) diff --git a/src/eatery/controllers/update_eatery.py b/src/eatery/controllers/update_eatery.py index be31278..7bb3752 100644 --- a/src/eatery/controllers/update_eatery.py +++ b/src/eatery/controllers/update_eatery.py @@ -72,6 +72,7 @@ def upload_image(self, image): >> left merge Eatery and CornellDiningNow >> left merge Events and CornellDiningNow """ + def compare(self): pass diff --git a/src/eatery/datatype/Eatery.py b/src/eatery/datatype/Eatery.py index b0a80bd..20f1413 100644 --- a/src/eatery/datatype/Eatery.py +++ b/src/eatery/datatype/Eatery.py @@ -3,9 +3,10 @@ from typing import Optional import pytz -#from event.datatype.Event import Event, filter_range -#from api.datatype.WaitTimesDay import WaitTimesDay -#from eatery.datatype.EateryAlert import EateryAlert + +# from event.datatype.Event import Event, filter_range +# from api.datatype.WaitTimesDay import WaitTimesDay +# from eatery.datatype.EateryAlert import EateryAlert class EateryID(Enum): @@ -49,4 +50,3 @@ class EateryID(Enum): MORRISON_DINING = 39 NOVICKS_CAFE = 40 VET_CAFE = 41 - diff --git a/src/eatery/datatype/EateryAlert.py b/src/eatery/datatype/EateryAlert.py index 2bb511b..ffd307b 100644 --- a/src/eatery/datatype/EateryAlert.py +++ b/src/eatery/datatype/EateryAlert.py @@ -1,11 +1,7 @@ class EateryAlert: def __init__( - self, - id: int, - description: str, - start_timestamp: int, - end_timestamp: int + self, id: int, description: str, start_timestamp: int, end_timestamp: int ): self.id = id self.description = description @@ -17,14 +13,14 @@ def to_json(self): "id": 1, "description": self.description, "start_timestamp": self.start_timestamp, - "end_timestamp": self.end_timestamp + "end_timestamp": self.end_timestamp, } @staticmethod def from_json(alert_json): return EateryAlert( - id = alert_json["id"], + id=alert_json["id"], description=alert_json["description"], start_timestamp=alert_json["start_timestamp"], - end_timestamp=alert_json["end_timestamp"] + end_timestamp=alert_json["end_timestamp"], ) diff --git a/src/eatery/migrations/0001_initial.py b/src/eatery/migrations/0001_initial.py index ca330b0..87246d5 100644 --- a/src/eatery/migrations/0001_initial.py +++ b/src/eatery/migrations/0001_initial.py @@ -7,25 +7,41 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Eatery', + name="Eatery", fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=40)), - ('menu_summary', models.TextField(blank=True, default='', null=True)), - ('image_url', models.URLField(blank=True)), - ('location', models.TextField(blank=True)), - ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), - ('online_order_url', models.URLField(blank=True, null=True)), - ('latitude', models.FloatField(blank=True, null=True)), - ('longitude', models.FloatField(blank=True, null=True)), - ('payment_accepts_meal_swipes', models.BooleanField(blank=True, null=True)), - ('payment_accepts_brbs', models.BooleanField(blank=True, null=True)), - ('payment_accepts_cash', models.BooleanField(blank=True, null=True)), + ("id", models.IntegerField(primary_key=True, serialize=False)), + ("name", models.CharField(max_length=40)), + ("menu_summary", models.TextField(blank=True, default="", null=True)), + ("image_url", models.URLField(blank=True)), + ("location", models.TextField(blank=True)), + ( + "campus_area", + models.CharField( + blank=True, + choices=[ + ("West", "West"), + ("North", "North"), + ("Central", "Central"), + ("Collegetown", "Collegetown"), + ("", "None"), + ], + default="", + max_length=15, + ), + ), + ("online_order_url", models.URLField(blank=True, null=True)), + ("latitude", models.FloatField(blank=True, null=True)), + ("longitude", models.FloatField(blank=True, null=True)), + ( + "payment_accepts_meal_swipes", + models.BooleanField(blank=True, null=True), + ), + ("payment_accepts_brbs", models.BooleanField(blank=True, null=True)), + ("payment_accepts_cash", models.BooleanField(blank=True, null=True)), ], ), ] diff --git a/src/eatery/migrations/0002_alter_eatery_id.py b/src/eatery/migrations/0002_alter_eatery_id.py index 714af0b..2b70b83 100644 --- a/src/eatery/migrations/0002_alter_eatery_id.py +++ b/src/eatery/migrations/0002_alter_eatery_id.py @@ -6,13 +6,15 @@ class Migration(migrations.Migration): dependencies = [ - ('eatery', '0001_initial'), + ("eatery", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='eatery', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + model_name="eatery", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), ), ] diff --git a/src/eatery/migrations/0003_alter_eatery_image_url.py b/src/eatery/migrations/0003_alter_eatery_image_url.py index f5e1c81..c276bb6 100644 --- a/src/eatery/migrations/0003_alter_eatery_image_url.py +++ b/src/eatery/migrations/0003_alter_eatery_image_url.py @@ -6,13 +6,16 @@ class Migration(migrations.Migration): dependencies = [ - ('eatery', '0002_alter_eatery_id'), + ("eatery", "0002_alter_eatery_id"), ] operations = [ migrations.AlterField( - model_name='eatery', - name='image_url', - field=models.URLField(blank=True, default='https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg'), + model_name="eatery", + name="image_url", + field=models.URLField( + blank=True, + default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg", + ), ), ] diff --git a/src/eatery/migrations/0004_alter_eatery_campus_area.py b/src/eatery/migrations/0004_alter_eatery_campus_area.py index c83d70d..77d43bd 100644 --- a/src/eatery/migrations/0004_alter_eatery_campus_area.py +++ b/src/eatery/migrations/0004_alter_eatery_campus_area.py @@ -6,13 +6,25 @@ class Migration(migrations.Migration): dependencies = [ - ('eatery', '0003_alter_eatery_image_url'), + ("eatery", "0003_alter_eatery_image_url"), ] operations = [ migrations.AlterField( - model_name='eatery', - name='campus_area', - field=models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('East', 'East'), ('', 'None')], default='', max_length=15), + model_name="eatery", + name="campus_area", + field=models.CharField( + blank=True, + choices=[ + ("West", "West"), + ("North", "North"), + ("Central", "Central"), + ("Collegetown", "Collegetown"), + ("East", "East"), + ("", "None"), + ], + default="", + max_length=15, + ), ), ] diff --git a/src/eatery/migrations/0005_alter_eatery_campus_area.py b/src/eatery/migrations/0005_alter_eatery_campus_area.py index 55187b0..df3bfb1 100644 --- a/src/eatery/migrations/0005_alter_eatery_campus_area.py +++ b/src/eatery/migrations/0005_alter_eatery_campus_area.py @@ -6,13 +6,24 @@ class Migration(migrations.Migration): dependencies = [ - ('eatery', '0004_alter_eatery_campus_area'), + ("eatery", "0004_alter_eatery_campus_area"), ] operations = [ migrations.AlterField( - model_name='eatery', - name='campus_area', - field=models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15), + model_name="eatery", + name="campus_area", + field=models.CharField( + blank=True, + choices=[ + ("West", "West"), + ("North", "North"), + ("Central", "Central"), + ("Collegetown", "Collegetown"), + ("", "None"), + ], + default="", + max_length=15, + ), ), ] diff --git a/src/eatery/models.py b/src/eatery/models.py index 731858a..6f4ea1e 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -2,6 +2,7 @@ from django.db import connection from eatery.util.constants import DEFAULT_IMAGE_URL + class Eatery(models.Model): class CampusArea(models.TextChoices): WEST = "West" @@ -23,6 +24,6 @@ class CampusArea(models.TextChoices): payment_accepts_meal_swipes = models.BooleanField(null=True, blank=True) payment_accepts_brbs = models.BooleanField(null=True, blank=True) payment_accepts_cash = models.BooleanField(null=True, blank=True) - + def __str__(self): return self.name diff --git a/src/eatery/permissions.py b/src/eatery/permissions.py index 42af072..80a9513 100644 --- a/src/eatery/permissions.py +++ b/src/eatery/permissions.py @@ -1,13 +1,14 @@ from rest_framework import permissions + class EateryPermission(permissions.BasePermission): def has_permission(self, request, view): - if view.action in ['list', 'retrieve']: + if view.action in ["list", "retrieve"]: return True return request.user.is_staff - + def has_object_permission(self, request, view, obj): - if view.action in ['retrieve']: + if view.action in ["retrieve"]: return True - return request.user.is_staff \ No newline at end of file + return request.user.is_staff diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index dbe4c0d..9f6dae0 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -1,15 +1,23 @@ from rest_framework import serializers from eatery.models import Eatery from event.models import Event -from event.serializers import EventSerializer, EventSerializerSimple, EventSerializerOptimized +from event.serializers import ( + EventSerializer, + EventSerializerSimple, + EventSerializerOptimized, +) from datetime import timedelta, datetime from zoneinfo import ZoneInfo + class EaterySerializer(serializers.ModelSerializer): id = serializers.IntegerField() name = serializers.CharField() - menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") - image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + menu_summary = serializers.CharField(allow_null=True, default="Cornell Eatery") + image_url = serializers.URLField( + allow_null=True, + default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg", + ) location = serializers.CharField(allow_null=True) campus_area = serializers.CharField(allow_null=True) online_order_url = serializers.URLField(allow_null=True) @@ -24,48 +32,115 @@ class EaterySerializer(serializers.ModelSerializer): def create(self, validated_data): eatery, _ = Eatery.objects.get_or_create(**validated_data) return eatery - + class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] - + fields = [ + "id", + "name", + "menu_summary", + "image_url", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + "events", + ] + + class EaterySerializerOptimized(serializers.ModelSerializer): events = EventSerializerOptimized(many=True, read_only=True) class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + fields = [ + "id", + "name", + "menu_summary", + "image_url", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + "events", + ] class EaterySerializerSimple(serializers.ModelSerializer): - menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") - image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + menu_summary = serializers.CharField(allow_null=True, default="Cornell Eatery") + image_url = serializers.URLField( + allow_null=True, + default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg", + ) events = EventSerializerSimple(many=True, read_only=True) class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + fields = [ + "id", + "name", + "menu_summary", + "image_url", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + "events", + ] + class EaterySerializerByDay(serializers.ModelSerializer): - menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") - image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + menu_summary = serializers.CharField(allow_null=True, default="Cornell Eatery") + image_url = serializers.URLField( + allow_null=True, + default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg", + ) events = serializers.SerializerMethodField() def get_events(self, obj): day_offset = self.context.get("day") now = datetime.now(ZoneInfo("America/New_York")) - day = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=day_offset) + day = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta( + days=day_offset + ) day_unix = int(day.timestamp()) day_end_unix = int((day + timedelta(days=1)).timestamp()) print(f"Now: {now}") print(f"Day: {day}") print(f"Day Unix: {day_unix}") print(f"Day End Unix: {day_end_unix}") - events = Event.objects.filter(eatery=obj.id, start__gte=day_unix, start__lt=day_end_unix) + events = Event.objects.filter( + eatery=obj.id, start__gte=day_unix, start__lt=day_end_unix + ) serializer = EventSerializerOptimized(instance=events, many=True) return serializer.data class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] - + fields = [ + "id", + "name", + "menu_summary", + "image_url", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + "events", + ] diff --git a/src/eatery/urls.py b/src/eatery/urls.py index e4519cf..5bed8fd 100644 --- a/src/eatery/urls.py +++ b/src/eatery/urls.py @@ -1,21 +1,15 @@ from django.urls import path from eatery.views import EateryViewSet, GetEateriesSimple, GetEateriesByDay -eateries_list = EateryViewSet.as_view({ - 'get':'list', - 'post': 'create' -}) +eateries_list = EateryViewSet.as_view({"get": "list", "post": "create"}) -eatery_list = EateryViewSet.as_view({ - 'get':'retrieve', - 'put':'update', - 'patch':'partial_update', - 'delete':'destroy' -}) +eatery_list = EateryViewSet.as_view( + {"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"} +) urlpatterns = [ - path("", eateries_list, name='eateries-list'), - path("/", eatery_list, name='eatery-list'), - path("simple/", GetEateriesSimple.as_view(), name='eateries-simple'), + path("", eateries_list, name="eateries-list"), + path("/", eatery_list, name="eatery-list"), + path("simple/", GetEateriesSimple.as_view(), name="eateries-simple"), path("day//", GetEateriesByDay.as_view(), name="eateries-day"), -] \ No newline at end of file +] diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index 76e95ff..e9690e7 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -3,12 +3,23 @@ from eatery.datatype.Eatery import EateryID CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" -CORNELL_VENDOR_URL = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" +CORNELL_VENDOR_URL = ( + "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" +) -DAY_OF_WEEK_LIST = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] +DAY_OF_WEEK_LIST = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", +] DEFAULT_IMAGE_URL = "https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg" + class SnapshotFileName(Enum): EATERY_STORE = "eatery_store.txt" ALERT_STORE = "alert_store.txt" diff --git a/src/eatery/util/convert_from_json.py b/src/eatery/util/convert_from_json.py index 9134b41..7fca648 100644 --- a/src/eatery/util/convert_from_json.py +++ b/src/eatery/util/convert_from_json.py @@ -22,4 +22,4 @@ def from_json(obj: Union[list, dict], *args, **kwargs): return Event.from_json(obj) def description(self): - return "ConvertFromJson""" \ No newline at end of file + return "ConvertFromJson""" diff --git a/src/eatery/util/time.py b/src/eatery/util/time.py index 7fce11d..b03a0dd 100644 --- a/src/eatery/util/time.py +++ b/src/eatery/util/time.py @@ -1,5 +1,6 @@ from datetime import date, time, datetime import pytz + def combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) diff --git a/src/eatery/views.py b/src/eatery/views.py index db04c35..85fd639 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -1,4 +1,9 @@ -from eatery.serializers import EaterySerializer, EaterySerializerSimple, EaterySerializerByDay, EaterySerializerOptimized +from eatery.serializers import ( + EaterySerializer, + EaterySerializerSimple, + EaterySerializerByDay, + EaterySerializerOptimized, +) from eatery.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse from django.shortcuts import get_object_or_404 @@ -12,10 +17,12 @@ from eatery.models import Eatery from .controllers.update_eatery import UpdateEateryController + class EateryViewSet(viewsets.ModelViewSet): """ View and edit eateries (all, or specific) """ + queryset = Eatery.objects.all() serializer_class = EaterySerializer permission_classes = [EateryPermission] @@ -25,7 +32,7 @@ def retrieve(self, request, *args, **kwargs): serializer = EaterySerializerOptimized(instance) return Response(serializer.data) - @method_decorator(cache_page(60*60*2)) # cache for 2 hours + @method_decorator(cache_page(60 * 60 * 2)) # cache for 2 hours def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) serializer = EaterySerializerOptimized(queryset, many=True) @@ -36,10 +43,10 @@ def get_object(self): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field assert lookup_url_kwarg in self.kwargs, ( - 'Expected view %s to be called with a URL keyword argument ' + "Expected view %s to be called with a URL keyword argument " 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - 'attribute on the view correctly.' % - (self.__class__.__name__, lookup_url_kwarg) + "attribute on the view correctly." + % (self.__class__.__name__, lookup_url_kwarg) ) # Uses the lookup_field attribute, which defaults to `pk` filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} @@ -84,19 +91,27 @@ def update(self, request, *args, **kwargs): except Exception as e: return JsonResponse(error_json(str(e))) + class GetEateriesSimple(APIView): """ View all eateries with less information """ + def get(self, request): eateries = EaterySerializerSimple(Eatery.objects.all(), many=True) return Response(eateries.data) - + + class GetEateriesByDay(APIView): """ Get all eatery information by day """ - @method_decorator(cache_page(60*60*2)) # cache for 2 hours + + @method_decorator(cache_page(60 * 60 * 2)) # cache for 2 hours def get(self, request, day): - eateries = EaterySerializerByDay(Eatery.objects.exclude(events__event_description="Open"), many=True, context={"day": day}) - return Response(eateries.data) \ No newline at end of file + eateries = EaterySerializerByDay( + Eatery.objects.exclude(events__event_description="Open"), + many=True, + context={"day": day}, + ) + return Response(eateries.data) diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index 4191bd0..952a733 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand from datetime import datetime -import requests +import requests from eatery.util.constants import CORNELL_DINING_URL from event.models import Event from eatery.controllers.populate_eatery import PopulateEateryController @@ -8,65 +8,81 @@ from item.controllers.populate_item import PopulateItemController from category.controllers.populate_category import PopulateCategoryController + class Command(BaseCommand): - help = 'Populates all models' - def handle(self, *args, **kwargs): - self.stdout.write(f"Populating models at {datetime.now()} UTC") - start = int(datetime.now().timestamp()) - self.process() - self.stdout.write(f"Finished populating models at {datetime.now()} UTC ({int(datetime.now().timestamp()) - start}s)") - - def get_json(self): - try: - response = requests.get(CORNELL_DINING_URL) - except Exception as e: - raise e - if response.status_code <= 400: - response = response.json() - json_eateries = response["data"]["eateries"] - return json_eateries - - def logger_wrapper(self, command_obj, log_title, args): - pre = int(datetime.now().timestamp()) - print(f"{datetime.now()} UTC: {log_title}") - output = command_obj.process(*args) - print(f"Done ({int(datetime.now().timestamp()) - pre}s) ") - return output - - def process(self): - """ - 1. Get JSON from API - - 2. create eateries (fron CDN json) - - 3. create events (from CDN json) - return events_dict = { eatery_id : [event, event, event...], eatery_id : ... } - - 4. create menus for every eatery's events - return menus_dict = { eatery_id : [menu, menu, menu...] } - - 5. create categories in each menu - return categories_dict = - { eatery_id : - { menu[i] : {"category_name" : id, "category_name" : id...}, - menu[i] : {"category_name" : id...} - } - } - - 6. create items for each category - - """ - - json_eateries = self.get_json() - - Event.truncate() - - self.logger_wrapper(PopulateEateryController(), "Populating eateries", [json_eateries]) - - events_dict = self.logger_wrapper(PopulateEventController(), "Populating events", [json_eateries]) - - categories_dict = self.logger_wrapper(PopulateCategoryController(), "Populating categories", [events_dict, json_eateries]) - - self.logger_wrapper(PopulateItemController(), "Populating items", [categories_dict, json_eateries]) - - print("Done populating") + help = "Populates all models" + + def handle(self, *args, **kwargs): + self.stdout.write(f"Populating models at {datetime.now()} UTC") + start = int(datetime.now().timestamp()) + self.process() + self.stdout.write( + f"Finished populating models at {datetime.now()} UTC ({int(datetime.now().timestamp()) - start}s)" + ) + + def get_json(self): + try: + response = requests.get(CORNELL_DINING_URL) + except Exception as e: + raise e + if response.status_code <= 400: + response = response.json() + json_eateries = response["data"]["eateries"] + return json_eateries + + def logger_wrapper(self, command_obj, log_title, args): + pre = int(datetime.now().timestamp()) + print(f"{datetime.now()} UTC: {log_title}") + output = command_obj.process(*args) + print(f"Done ({int(datetime.now().timestamp()) - pre}s) ") + return output + + def process(self): + """ + 1. Get JSON from API + + 2. create eateries (fron CDN json) + + 3. create events (from CDN json) + return events_dict = { eatery_id : [event, event, event...], eatery_id : ... } + + 4. create menus for every eatery's events + return menus_dict = { eatery_id : [menu, menu, menu...] } + + 5. create categories in each menu + return categories_dict = + { eatery_id : + { menu[i] : {"category_name" : id, "category_name" : id...}, + menu[i] : {"category_name" : id...} + } + } + + 6. create items for each category + + """ + + json_eateries = self.get_json() + + Event.truncate() + + self.logger_wrapper( + PopulateEateryController(), "Populating eateries", [json_eateries] + ) + + events_dict = self.logger_wrapper( + PopulateEventController(), "Populating events", [json_eateries] + ) + + categories_dict = self.logger_wrapper( + PopulateCategoryController(), + "Populating categories", + [events_dict, json_eateries], + ) + + self.logger_wrapper( + PopulateItemController(), + "Populating items", + [categories_dict, json_eateries], + ) + + print("Done populating") diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 53dbae8..3101d88 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -40,7 +40,6 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework.authtoken", - # Apps "eatery_blue_backend", "eatery", @@ -49,7 +48,6 @@ "item", "category", "person", - # Third party "rest_framework", ] diff --git a/src/event/apps.py b/src/event/apps.py index 766f251..52dbc82 100644 --- a/src/event/apps.py +++ b/src/event/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig + class EventConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' + default_auto_field = "django.db.models.BigAutoField" name = "event" - diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py index 8823920..b98a25e 100644 --- a/src/event/controllers/populate_event.py +++ b/src/event/controllers/populate_event.py @@ -4,7 +4,8 @@ import json import pytz -class PopulateEventController(): + +class PopulateEventController: def __init__(self): self = self @@ -13,33 +14,34 @@ def generate_events(self, json_eatery): From an eatery json from CDN, create events for that eatery and add to event model. """ - #events = [ event obj, event, event ... ] for an eatery. + # events = [ event obj, event, event ... ] for an eatery. events = [] json_dates = json_eatery["operatingHours"] for json_date in json_dates: json_events = json_date["events"] - + for json_event in json_events: # Create an event: eatery_id = dining_id_to_internal_id(json_eatery["id"]).value data = { - 'eatery': eatery_id, - 'event_description': json_event["descr"], - 'start' : int(json_event["startTimestamp"]), - 'end' : int(json_event["endTimestamp"])} + "eatery": eatery_id, + "event_description": json_event["descr"], + "start": int(json_event["startTimestamp"]), + "end": int(json_event["endTimestamp"]), + } event = EventSerializer(data=data) - + if event.is_valid(): event.save() else: print(event.errors) return event.errors - - events.append(event.data["id"]) + + events.append(event.data["id"]) return events - + def generate_external_events(self, json_eatery): json_dates = json_eatery["operatingHours"] events = [] @@ -48,47 +50,60 @@ def generate_external_events(self, json_eatery): date = datetime.now() while date.strftime("%A").lower() != json_date["weekday"].lower(): date += timedelta(days=1) - start_string = json_event['start'] - timezone = pytz.timezone('US/Eastern') - start_time = datetime(date.year, date.month, date.day, int(start_string[:2]), int(start_string[3:])) + start_string = json_event["start"] + timezone = pytz.timezone("US/Eastern") + start_time = datetime( + date.year, + date.month, + date.day, + int(start_string[:2]), + int(start_string[3:]), + ) start_timestamp = timezone.localize(start_time).timestamp() - end_string = json_event['end'] + end_string = json_event["end"] if int(end_string[:2]) < int(start_string[:2]): date += timedelta(days=1) - end_time = datetime(date.year, date.month, date.day, int(end_string[:2]), int(end_string[3:])) + end_time = datetime( + date.year, + date.month, + date.day, + int(end_string[:2]), + int(end_string[3:]), + ) end_timestamp = timezone.localize(end_time).timestamp() eatery_id = json_eatery["id"] data = { - 'eatery': eatery_id, - 'event_description': json_event["descr"], - 'start' : start_timestamp, - 'end' : end_timestamp} + "eatery": eatery_id, + "event_description": json_event["descr"], + "start": start_timestamp, + "end": end_timestamp, + } event = EventSerializer(data=data) - + if event.is_valid(): event.save() else: print(event.errors) return event.errors - + events.append(event.data["id"]) return events def process(self, json_eateries): - #events_dict { eatery_id : [event, event, event...], eatery_id : ... } + # events_dict { eatery_id : [event, event, event...], eatery_id : ... } events_dict = {} for json_eatery in json_eateries: eatery_id = int(json_eatery["id"]) events = self.generate_events(json_eatery) - events_dict[eatery_id] = events + events_dict[eatery_id] = events # create custom events for external eateries with open("./static_sources/external_eateries.json", "r") as file: json_obj = json.load(file) - for eatery in json_obj['eateries']: - events_dict[eatery['id']] = self.generate_external_events(eatery) + for eatery in json_obj["eateries"]: + events_dict[eatery["id"]] = self.generate_external_events(eatery) - return events_dict + return events_dict diff --git a/src/event/controllers/update_models/CornellDiningEvents.py b/src/event/controllers/update_models/CornellDiningEvents.py index 2ae6387..40301b3 100644 --- a/src/event/controllers/update_models/CornellDiningEvents.py +++ b/src/event/controllers/update_models/CornellDiningEvents.py @@ -48,7 +48,8 @@ def parse_eatery(json_eatery: dict) -> Eatery: @staticmethod def eatery_events_from_json( - json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: + json_operating_hours: list, json_dining_items: list, is_cafe: bool + ) -> list[Event]: json_operating_hours = sorted( json_operating_hours, key=lambda json_date_events: json_date_events["date"] ) @@ -114,10 +115,7 @@ def dining_hall_menu_from_json(json_menu: list) -> Menu: @staticmethod def from_cornell_dining_json(json_item: dict): - return MenuItem( - healthy=json_item["healthy"], - name=json_item["item"] - ) + return MenuItem(healthy=json_item["healthy"], name=json_item["item"]) def description(self): return "CornellDiningEvents" diff --git a/src/event/controllers/update_models/schedule/CornellDiningEvents.py b/src/event/controllers/update_models/schedule/CornellDiningEvents.py index 7101169..40301b3 100644 --- a/src/event/controllers/update_models/schedule/CornellDiningEvents.py +++ b/src/event/controllers/update_models/schedule/CornellDiningEvents.py @@ -115,10 +115,7 @@ def dining_hall_menu_from_json(json_menu: list) -> Menu: @staticmethod def from_cornell_dining_json(json_item: dict): - return MenuItem( - healthy=json_item["healthy"], - name=json_item["item"] - ) + return MenuItem(healthy=json_item["healthy"], name=json_item["item"]) def description(self): return "CornellDiningEvents" diff --git a/src/event/controllers/update_models/schedule/RepeatingSchedule.py b/src/event/controllers/update_models/schedule/RepeatingSchedule.py index 6b27a8a..58c5d10 100644 --- a/src/event/controllers/update_models/schedule/RepeatingSchedule.py +++ b/src/event/controllers/update_models/schedule/RepeatingSchedule.py @@ -14,9 +14,9 @@ def __init__(self, eatery_id: EateryID, cache): def __call__(self, *args, **kwargs) -> list[Eatery]: if "day_of_week_schedules" not in self.cache: - self.cache[ - "day_of_week_schedules" - ] = RepeatingEventSchedule.objects.all().values() + self.cache["day_of_week_schedules"] = ( + RepeatingEventSchedule.objects.all().values() + ) repeating_schedules = [ sched for sched in self.cache["day_of_week_schedules"] diff --git a/src/event/datatype/Event.py b/src/event/datatype/Event.py index 02e8024..79bb19d 100644 --- a/src/event/datatype/Event.py +++ b/src/event/datatype/Event.py @@ -3,7 +3,8 @@ import pytz from datatype.Menu import Menu -#from api.util.time import combined_timestamp + +# from api.util.time import combined_timestamp class Event: @@ -114,4 +115,4 @@ def filter_range( else: raise Exception( f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}" - ) \ No newline at end of file + ) diff --git a/src/event/datatype/Menu.py b/src/event/datatype/Menu.py index 118d378..6e77a77 100644 --- a/src/event/datatype/Menu.py +++ b/src/event/datatype/Menu.py @@ -1,5 +1,6 @@ from datatype.MenuCategory import MenuCategory + class Menu: def __init__(self, categories: list[MenuCategory]): diff --git a/src/event/datatype/MenuCategory.py b/src/event/datatype/MenuCategory.py index f2cd1cc..e54262a 100644 --- a/src/event/datatype/MenuCategory.py +++ b/src/event/datatype/MenuCategory.py @@ -10,12 +10,12 @@ def __init__(self, category: str, items: list[MenuItem]): def to_json(self): return { "category": self.category, - "items": [item.to_json() for item in self.items] + "items": [item.to_json() for item in self.items], } @staticmethod def from_json(category_json): return MenuCategory( category=category_json["category"], - items=[MenuItem.from_json(item) for item in category_json["items"]] + items=[MenuItem.from_json(item) for item in category_json["items"]], ) diff --git a/src/event/datatype/MenuItem.py b/src/event/datatype/MenuItem.py index 34124db..72b8b33 100644 --- a/src/event/datatype/MenuItem.py +++ b/src/event/datatype/MenuItem.py @@ -2,15 +2,16 @@ from api.datatype.MenuItemSection import MenuItemSection + class MenuItem: def __init__( - self, - name: str, - healthy: Optional[bool] = None, - base_price: Optional[float] = None, - description: Optional[str] = None, - sections: Optional[MenuItemSection] = None + self, + name: str, + healthy: Optional[bool] = None, + base_price: Optional[float] = None, + description: Optional[str] = None, + sections: Optional[MenuItemSection] = None, ): self.healthy = healthy self.name = name @@ -24,7 +25,11 @@ def to_json(self): "name": self.name, "base_price": self.base_price, "description": self.description, - "sections": None if self.sections is None else [section.to_json() for section in self.sections] + "sections": ( + None + if self.sections is None + else [section.to_json() for section in self.sections] + ), } @staticmethod @@ -34,6 +39,12 @@ def from_json(item_json): healthy=item_json["healthy"], base_price=item_json["base_price"], description=item_json["description"], - sections=None if "sections" not in item_json or item_json["sections"] is None - else [MenuItemSection.from_json(section) for section in item_json["sections"]] - ) + sections=( + None + if "sections" not in item_json or item_json["sections"] is None + else [ + MenuItemSection.from_json(section) + for section in item_json["sections"] + ] + ), + ) diff --git a/src/event/datatype/MenuItemSection.py b/src/event/datatype/MenuItemSection.py index 770c376..1ac44af 100644 --- a/src/event/datatype/MenuItemSection.py +++ b/src/event/datatype/MenuItemSection.py @@ -1,5 +1,6 @@ from api.datatype.MenuSubItem import MenuSubItem + class MenuItemSection: def __init__(self, name: str, subitems: list[MenuSubItem]): @@ -9,12 +10,12 @@ def __init__(self, name: str, subitems: list[MenuSubItem]): def to_json(self): return { "name": self.name, - "subitems": [item.to_json() for item in self.subitems] + "subitems": [item.to_json() for item in self.subitems], } @staticmethod def from_json(section_json): return MenuItemSection( name=section_json["name"], - subitems=[MenuSubItem.from_json(item) for item in section_json["subitems"]] + subitems=[MenuSubItem.from_json(item) for item in section_json["subitems"]], ) diff --git a/src/event/datatype/MenuSubItem.py b/src/event/datatype/MenuSubItem.py index 854b0ed..7c82c28 100644 --- a/src/event/datatype/MenuSubItem.py +++ b/src/event/datatype/MenuSubItem.py @@ -1,12 +1,10 @@ from typing import Optional + class MenuSubItem: def __init__( - self, - name: str, - total_price: Optional[float], - additional_price: Optional[float] + self, name: str, total_price: Optional[float], additional_price: Optional[float] ): self.name = name self.total_price = total_price @@ -16,7 +14,7 @@ def to_json(self): return { "name": self.name, "total_price": self.total_price, - "additional_price": self.additional_price + "additional_price": self.additional_price, } @staticmethod @@ -24,5 +22,5 @@ def from_json(item_json): return MenuSubItem( name=item_json["name"], total_price=item_json.get("total_price"), - additional_price=item_json.get("additional_price") + additional_price=item_json.get("additional_price"), ) diff --git a/src/event/datatype/WaitTime.py b/src/event/datatype/WaitTime.py index 7e5cfca..70c8265 100644 --- a/src/event/datatype/WaitTime.py +++ b/src/event/datatype/WaitTime.py @@ -1,10 +1,10 @@ class WaitTime: def __init__( - self, - timestamp: int, - wait_time_low: float, - wait_time_expected: float, - wait_time_high: float + self, + timestamp: int, + wait_time_low: float, + wait_time_expected: float, + wait_time_high: float, ): self.timestamp = timestamp self.wait_time_low = wait_time_low @@ -16,7 +16,7 @@ def to_json(self): "timestamp": self.timestamp, "wait_time_low": self.wait_time_low, "wait_time_expected": self.wait_time_expected, - "wait_time_high": self.wait_time_high + "wait_time_high": self.wait_time_high, } @staticmethod @@ -25,5 +25,5 @@ def from_json(wait_time_json): timestamp=wait_time_json["timestamp"], wait_time_low=wait_time_json["wait_time_low"], wait_time_expected=wait_time_json["wait_time_expected"], - wait_time_high=wait_time_json["wait_time_high"] + wait_time_high=wait_time_json["wait_time_high"], ) diff --git a/src/event/datatype/WaitTimesDay.py b/src/event/datatype/WaitTimesDay.py index cb9ea86..e3c724c 100644 --- a/src/event/datatype/WaitTimesDay.py +++ b/src/event/datatype/WaitTimesDay.py @@ -3,23 +3,22 @@ class WaitTimesDay: - def __init__( - self, - canonical_date: date, - data: list[WaitTime] - ): + def __init__(self, canonical_date: date, data: list[WaitTime]): self.canonical_date = canonical_date self.data = data def to_json(self): return { "canonical_date": str(self.canonical_date), - "data": [wait_time.to_json() for wait_time in self.data] + "data": [wait_time.to_json() for wait_time in self.data], } @staticmethod def from_json(wait_times_day_json): return WaitTimesDay( canonical_date=date.fromisoformat(wait_times_day_json["canonical_date"]), - data=[WaitTime.from_json(wait_time) for wait_time in wait_times_day_json["data"]] + data=[ + WaitTime.from_json(wait_time) + for wait_time in wait_times_day_json["data"] + ], ) diff --git a/src/event/migrations/0001_initial.py b/src/event/migrations/0001_initial.py index fc114f2..e5a3879 100644 --- a/src/event/migrations/0001_initial.py +++ b/src/event/migrations/0001_initial.py @@ -9,18 +9,41 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('eatery', '0001_initial'), + ("eatery", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Event', + name="Event", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('event_description', models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', null=True)), - ('start', models.IntegerField(default=0)), - ('end', models.IntegerField(default=0)), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='events', to='eatery.eatery')), + ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "event_description", + models.TextField( + blank=True, + choices=[ + ("Breakfast", "Breakfast"), + ("Brunch", "Brunch"), + ("Lunch", "Lunch"), + ("Dinner", "Dinner"), + ("General", "General"), + ("Cafe", "Cafe"), + ("Pants", "Pants"), + ], + default="General", + null=True, + ), + ), + ("start", models.IntegerField(default=0)), + ("end", models.IntegerField(default=0)), + ( + "eatery", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="events", + to="eatery.eatery", + ), + ), ], ), ] diff --git a/src/event/migrations/0002_alter_event_id.py b/src/event/migrations/0002_alter_event_id.py index 6570118..77c5cbe 100644 --- a/src/event/migrations/0002_alter_event_id.py +++ b/src/event/migrations/0002_alter_event_id.py @@ -6,13 +6,15 @@ class Migration(migrations.Migration): dependencies = [ - ('event', '0001_initial'), + ("event", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='event', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + model_name="event", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), ), ] diff --git a/src/event/migrations/0003_alter_event_event_description.py b/src/event/migrations/0003_alter_event_event_description.py index 86afd1b..ef70791 100644 --- a/src/event/migrations/0003_alter_event_event_description.py +++ b/src/event/migrations/0003_alter_event_event_description.py @@ -6,13 +6,26 @@ class Migration(migrations.Migration): dependencies = [ - ('event', '0002_alter_event_id'), + ("event", "0002_alter_event_id"), ] operations = [ migrations.AlterField( - model_name='event', - name='event_description', - field=models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Open', 'Cafe'), ('Pants', 'Pants')], default='General', null=True), + model_name="event", + name="event_description", + field=models.TextField( + blank=True, + choices=[ + ("Breakfast", "Breakfast"), + ("Brunch", "Brunch"), + ("Lunch", "Lunch"), + ("Dinner", "Dinner"), + ("General", "General"), + ("Open", "Cafe"), + ("Pants", "Pants"), + ], + default="General", + null=True, + ), ), ] diff --git a/src/event/migrations/0004_alter_event_event_description.py b/src/event/migrations/0004_alter_event_event_description.py index b3f7d98..4a8ca04 100644 --- a/src/event/migrations/0004_alter_event_event_description.py +++ b/src/event/migrations/0004_alter_event_event_description.py @@ -6,13 +6,26 @@ class Migration(migrations.Migration): dependencies = [ - ('event', '0003_alter_event_event_description'), + ("event", "0003_alter_event_event_description"), ] operations = [ migrations.AlterField( - model_name='event', - name='event_description', - field=models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', null=True), + model_name="event", + name="event_description", + field=models.TextField( + blank=True, + choices=[ + ("Breakfast", "Breakfast"), + ("Brunch", "Brunch"), + ("Lunch", "Lunch"), + ("Dinner", "Dinner"), + ("General", "General"), + ("Cafe", "Cafe"), + ("Pants", "Pants"), + ], + default="General", + null=True, + ), ), ] diff --git a/src/event/models.py b/src/event/models.py index d71a303..e0a4d86 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -1,8 +1,9 @@ -from django.db import models +from django.db import models from django.db import connection from eatery.models import Eatery + class EventDescription(models.TextChoices): BREAKFAST = "Breakfast" BRUNCH = "Brunch" @@ -12,17 +13,24 @@ class EventDescription(models.TextChoices): CAFE = "Cafe" PANTS = "Pants" -class Event(models.Model): - eatery = models.ForeignKey(Eatery, related_name = "events", on_delete=models.DO_NOTHING) + +class Event(models.Model): + eatery = models.ForeignKey( + Eatery, related_name="events", on_delete=models.DO_NOTHING + ) event_description = models.TextField( - choices=EventDescription.choices, default = EventDescription.GENERAL, blank=True, null = True) - start = models.IntegerField(default = 0) - end = models.IntegerField(default = 0) - + choices=EventDescription.choices, + default=EventDescription.GENERAL, + blank=True, + null=True, + ) + start = models.IntegerField(default=0) + end = models.IntegerField(default=0) + def __str__(self): return f"{self.eatery.name}: {self.event_description} from {self.start} to {self.end}" @classmethod def truncate(cls): with connection.cursor() as cursor: - cursor.execute('TRUNCATE TABLE {} CASCADE'.format(cls._meta.db_table)) \ No newline at end of file + cursor.execute("TRUNCATE TABLE {} CASCADE".format(cls._meta.db_table)) diff --git a/src/event/serializers.py b/src/event/serializers.py index 435a044..65b0259 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -3,9 +3,12 @@ from category.serializers import CategorySerializer, CategorySerializerOptimized from datetime import datetime + class EventSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False, read_only=True) - event_description = serializers.CharField(allow_null=True, allow_blank=True, default=None) + event_description = serializers.CharField( + allow_null=True, allow_blank=True, default=None + ) start = serializers.IntegerField() end = serializers.IntegerField() menu = CategorySerializer(many=True, read_only=True) @@ -18,6 +21,7 @@ class Meta: model = Event fields = ["id", "eatery", "event_description", "start", "end", "menu"] + class EventSerializerOptimized(serializers.ModelSerializer): menu = CategorySerializerOptimized(many=True, read_only=True) @@ -25,6 +29,7 @@ class Meta: model = Event fields = ["id", "event_description", "start", "end", "menu"] + class EventSerializerSimple(serializers.ModelSerializer): class Meta: model = Event diff --git a/src/event/urls.py b/src/event/urls.py index 5c0709c..072081d 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -7,4 +7,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/event/views.py b/src/event/views.py index a0bdf91..85b3022 100644 --- a/src/event/views.py +++ b/src/event/views.py @@ -2,6 +2,7 @@ from event.models import Event from event.serializers import EventSerializer + class EventViewSet(viewsets.ModelViewSet): queryset = Event.objects.all() - serializer_class = EventSerializer \ No newline at end of file + serializer_class = EventSerializer diff --git a/src/item/apps.py b/src/item/apps.py index f290658..77040d0 100644 --- a/src/item/apps.py +++ b/src/item/apps.py @@ -2,5 +2,5 @@ class ItemConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'item' + default_auto_field = "django.db.models.BigAutoField" + name = "item" diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 132d775..6156eea 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -6,50 +6,46 @@ import json from util.constants import eatery_is_cafe -class PopulateItemController(): + +class PopulateItemController: def __init__(self): - self = self + self = self def generate_cafe_items(self, menu, json_eatery): - for json_item in json_eatery["diningItems"]: - category_name = json_item['category'].strip() + for json_item in json_eatery["diningItems"]: + category_name = json_item["category"].strip() try: category_id = menu[category_name] except KeyError: continue - data = { - "category" : category_id, - "name" : json_item["item"] - } + data = {"category": category_id, "name": json_item["item"]} item = ItemSerializer(data=data) if item.is_valid(): item.save() else: print(item.errors) - def generate_dining_hall_items(self, menu, json_event, json_eatery): - json_menus = json_event['menu'] + json_menus = json_event["menu"] for json_menu in json_menus: - category_name = json_menu['category'].strip() + category_name = json_menu["category"].strip() category_id = menu[category_name] - for json_item in json_menu['items']: - data = { - "category" : category_id, - "name" : json_item["item"] - } + for json_item in json_menu["items"]: + data = {"category": category_id, "name": json_item["item"]} item = ItemSerializer(data=data) if item.is_valid(): item.save() - else: - print(item.errors) + else: + print(item.errors) def process(self, categories_dict, json_eateries): - with open("./static_sources/external_eateries.json", "r") as external_eateries_file: + with open( + "./static_sources/external_eateries.json", "r" + ) as external_eateries_file: external_eateries_json = json.load(external_eateries_file) json_eateries.extend(external_eateries_json["eateries"]) @@ -65,14 +61,15 @@ def process(self, categories_dict, json_eateries): is_cafe = eatery_is_cafe(json_eatery) json_dates = json_eatery["operatingHours"] - for json_date in json_dates: + for json_date in json_dates: json_events = json_date["events"] for json_event in json_events: if i < len(iter): menu_id = iter[i] - menu = eatery_menus[menu_id]; i += 1 + menu = eatery_menus[menu_id] + i += 1 - if is_cafe: + if is_cafe: self.generate_cafe_items(menu, json_eatery) - else: + else: self.generate_dining_hall_items(menu, json_event, json_eatery) diff --git a/src/item/migrations/0001_initial.py b/src/item/migrations/0001_initial.py index 621174f..d9a19c6 100644 --- a/src/item/migrations/0001_initial.py +++ b/src/item/migrations/0001_initial.py @@ -9,17 +9,24 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('category', '0001_initial'), + ("category", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Item', + name="Item", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(default='Item', max_length=40)), - ('base_price', models.FloatField(blank=True, default=0.0, null=True)), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='items', to='category.category')), + ("id", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(default="Item", max_length=40)), + ("base_price", models.FloatField(blank=True, default=0.0, null=True)), + ( + "category", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="items", + to="category.category", + ), + ), ], ), ] diff --git a/src/item/migrations/0002_alter_item_id.py b/src/item/migrations/0002_alter_item_id.py index 7d59a20..8afff18 100644 --- a/src/item/migrations/0002_alter_item_id.py +++ b/src/item/migrations/0002_alter_item_id.py @@ -6,13 +6,15 @@ class Migration(migrations.Migration): dependencies = [ - ('item', '0001_initial'), + ("item", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='item', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + model_name="item", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), ), ] diff --git a/src/item/models.py b/src/item/models.py index 223539d..0ea8194 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -2,10 +2,13 @@ from eatery.models import Eatery from category.models import Category + class Item(models.Model): - category = models.ForeignKey(Category, related_name = "items", on_delete=models.DO_NOTHING) - name = models.CharField(max_length=40, default = "Item") + category = models.ForeignKey( + Category, related_name="items", on_delete=models.DO_NOTHING + ) + name = models.CharField(max_length=40, default="Item") base_price = models.FloatField(null=True, blank=True, default=0.0) - + def __str__(self): return f"{self.name} ({self.category.name})" diff --git a/src/item/serializers.py b/src/item/serializers.py index 519109e..620f86a 100644 --- a/src/item/serializers.py +++ b/src/item/serializers.py @@ -1,19 +1,21 @@ from rest_framework import serializers from item.models import Item + class ItemSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(read_only = True) - name = serializers.CharField(default = "Item") + id = serializers.IntegerField(read_only=True) + name = serializers.CharField(default="Item") def create(self, validated_data): - item, _ = Item.objects.get_or_create(**validated_data) + item, _ = Item.objects.get_or_create(**validated_data) return item class Meta: model = Item - fields = ['id', 'category', 'name'] + fields = ["id", "category", "name"] + class ItemSerializerOptimized(serializers.ModelSerializer): class Meta: model = Item - fields = ['id', 'name'] \ No newline at end of file + fields = ["id", "name"] diff --git a/src/item/urls.py b/src/item/urls.py index 6c5de78..3827d0a 100644 --- a/src/item/urls.py +++ b/src/item/urls.py @@ -7,4 +7,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/item/views.py b/src/item/views.py index 745b028..9ca7171 100644 --- a/src/item/views.py +++ b/src/item/views.py @@ -2,6 +2,7 @@ from item.models import Item from item.serializers import ItemSerializer + class ItemViewSet(viewsets.ModelViewSet): queryset = Item.objects.all() - serializer_class = ItemSerializer \ No newline at end of file + serializer_class = ItemSerializer diff --git a/src/manage.py b/src/manage.py index ad32ebf..b469b27 100755 --- a/src/manage.py +++ b/src/manage.py @@ -6,8 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", - "eatery_blue_backend.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/src/person/admin.py b/src/person/admin.py index 8b935f3..04a8de9 100644 --- a/src/person/admin.py +++ b/src/person/admin.py @@ -2,4 +2,4 @@ from person.models import Student, Chef admin.site.register(Student) -admin.site.register(Chef) \ No newline at end of file +admin.site.register(Chef) diff --git a/src/person/apps.py b/src/person/apps.py index b30fee4..8f12bd7 100644 --- a/src/person/apps.py +++ b/src/person/apps.py @@ -2,5 +2,5 @@ class PersonConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'person' + default_auto_field = "django.db.models.BigAutoField" + name = "person" diff --git a/src/person/migrations/0001_initial.py b/src/person/migrations/0001_initial.py index 0bbad96..f5a02be 100644 --- a/src/person/migrations/0001_initial.py +++ b/src/person/migrations/0001_initial.py @@ -9,28 +9,67 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('item', '0001_initial'), - ('eatery', '0001_initial'), - ('auth', '0012_alter_user_first_name_max_length'), + ("item", "0001_initial"), + ("eatery", "0001_initial"), + ("auth", "0012_alter_user_first_name_max_length"), ] operations = [ migrations.CreateModel( - name='Student', + name="Student", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('net_id', models.TextField()), - ('favorite_eateries', models.ManyToManyField(related_name='student', to='eatery.Eatery')), - ('favorite_items', models.ManyToManyField(related_name='student', to='item.Item')), - ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("net_id", models.TextField()), + ( + "favorite_eateries", + models.ManyToManyField(related_name="student", to="eatery.Eatery"), + ), + ( + "favorite_items", + models.ManyToManyField(related_name="student", to="item.Item"), + ), + ( + "user", + models.OneToOneField( + default=None, + on_delete=django.db.models.deletion.CASCADE, + to="auth.user", + ), + ), ], ), migrations.CreateModel( - name='Chef', + name="Chef", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('eateries_managed', models.ManyToManyField(related_name='chef', to='eatery.Eatery')), - ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "eateries_managed", + models.ManyToManyField(related_name="chef", to="eatery.Eatery"), + ), + ( + "user", + models.OneToOneField( + default=None, + on_delete=django.db.models.deletion.CASCADE, + to="auth.user", + ), + ), ], ), ] diff --git a/src/person/models.py b/src/person/models.py index 82067aa..69cad5d 100644 --- a/src/person/models.py +++ b/src/person/models.py @@ -4,16 +4,21 @@ class Student(models.Model): net_id = models.TextField() - favorite_eateries = models.ManyToManyField('eatery.Eatery', related_name='student') - favorite_items = models.ManyToManyField('item.Item', related_name='student') - user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) - + favorite_eateries = models.ManyToManyField("eatery.Eatery", related_name="student") + favorite_items = models.ManyToManyField("item.Item", related_name="student") + user = models.OneToOneField( + User, on_delete=models.CASCADE, unique=True, default=None + ) + def __str__(self): return f"Student {self.net_id}" + class Chef(models.Model): - eateries_managed = models.ManyToManyField('eatery.Eatery', related_name='chef') - user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) - + eateries_managed = models.ManyToManyField("eatery.Eatery", related_name="chef") + user = models.OneToOneField( + User, on_delete=models.CASCADE, unique=True, default=None + ) + def __str__(self): - return f"Chef {self.user.username}" \ No newline at end of file + return f"Chef {self.user.username}" diff --git a/src/person/serializers.py b/src/person/serializers.py index a5e3942..00061aa 100644 --- a/src/person/serializers.py +++ b/src/person/serializers.py @@ -3,12 +3,14 @@ from person.models import Student, Chef from django.contrib.auth.models import User + class StudentSerializer(serializers.ModelSerializer): class Meta: model = Student - fields = ['id', 'net_id', 'user', 'favorite_eateries', 'favorite_items'] + fields = ["id", "net_id", "user", "favorite_eateries", "favorite_items"] + class ChefSerializer(serializers.ModelSerializer): class Meta: model = Chef - fields = ['id', 'user', 'eateries_managed'] + fields = ["id", "user", "eateries_managed"] diff --git a/src/person/urls.py b/src/person/urls.py index 75e30dc..e219b74 100644 --- a/src/person/urls.py +++ b/src/person/urls.py @@ -8,4 +8,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/person/views.py b/src/person/views.py index 778d6e7..ec981a4 100644 --- a/src/person/views.py +++ b/src/person/views.py @@ -2,10 +2,12 @@ from person.models import Student, Chef from person.serializers import StudentSerializer, ChefSerializer + class StudentViewSet(viewsets.ModelViewSet): queryset = Student.objects.all() serializer_class = StudentSerializer + class ChefViewSet(viewsets.ModelViewSet): queryset = Chef.objects.all() - serializer_class = ChefSerializer \ No newline at end of file + serializer_class = ChefSerializer diff --git a/src/report/apps.py b/src/report/apps.py index a3e9973..f02dd49 100644 --- a/src/report/apps.py +++ b/src/report/apps.py @@ -2,5 +2,5 @@ class ReportConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = "report" \ No newline at end of file + default_auto_field = "django.db.models.BigAutoField" + name = "report" diff --git a/src/report/migrations/0001_initial.py b/src/report/migrations/0001_initial.py index 7fa1323..fbfe01a 100644 --- a/src/report/migrations/0001_initial.py +++ b/src/report/migrations/0001_initial.py @@ -9,17 +9,33 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('eatery', '0001_initial'), + ("eatery", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Report', + name="Report", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField()), - ('created', models.DateTimeField(auto_now_add=True)), - ('eatery', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='eatery.eatery')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField()), + ("created", models.DateTimeField(auto_now_add=True)), + ( + "eatery", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="eatery.eatery", + ), + ), ], ), ] diff --git a/src/report/migrations/0002_report_netid.py b/src/report/migrations/0002_report_netid.py index 22c3b63..1bfc648 100644 --- a/src/report/migrations/0002_report_netid.py +++ b/src/report/migrations/0002_report_netid.py @@ -6,13 +6,13 @@ class Migration(migrations.Migration): dependencies = [ - ('report', '0001_initial'), + ("report", "0001_initial"), ] operations = [ migrations.AddField( - model_name='report', - name='netid', + model_name="report", + name="netid", field=models.CharField(blank=True, max_length=10, null=True), ), ] diff --git a/src/report/models.py b/src/report/models.py index 6b81978..f331648 100644 --- a/src/report/models.py +++ b/src/report/models.py @@ -7,8 +7,6 @@ class Report(models.Model): netid = models.CharField(max_length=10, null=True, blank=True) content = models.TextField() created = models.DateTimeField(auto_now_add=True) - + def __str__(self): - return f'{self.content} - {self.created}' - - + return f"{self.content} - {self.created}" diff --git a/src/report/serializers.py b/src/report/serializers.py index 1356886..6e7c4a9 100644 --- a/src/report/serializers.py +++ b/src/report/serializers.py @@ -5,4 +5,4 @@ class ReportSerializer(serializers.ModelSerializer): class Meta: model = Report - fields = ['id', 'eatery', 'netid', 'content', 'created'] + fields = ["id", "eatery", "netid", "content", "created"] diff --git a/src/report/urls.py b/src/report/urls.py index e4a4df6..2977993 100644 --- a/src/report/urls.py +++ b/src/report/urls.py @@ -8,4 +8,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/util/constants.py b/src/util/constants.py index c658cf0..47c3f86 100644 --- a/src/util/constants.py +++ b/src/util/constants.py @@ -1,2 +1,4 @@ def eatery_is_cafe(json_eatery): - return not "Dining Room" in [eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]] \ No newline at end of file + return not "Dining Room" in [ + eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] + ] From 10bf07dd4757a7dd497f6dc59be6cd8975bb70c7 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 5 Sep 2024 12:10:35 -0400 Subject: [PATCH 246/305] Running black on all files --- src/category/apps.py | 4 +- src/category/controllers/populate_category.py | 24 ++- src/category/migrations/0001_initial.py | 17 ++- .../migrations/0002_alter_category_id.py | 10 +- src/category/models.py | 2 +- src/category/serializers.py | 1 + src/category/urls.py | 2 +- src/category/views.py | 3 +- src/eatery/controllers/populate_eatery.py | 3 +- src/eatery/controllers/update_eatery.py | 1 + src/eatery/datatype/Eatery.py | 8 +- src/eatery/datatype/EateryAlert.py | 12 +- src/eatery/migrations/0001_initial.py | 46 ++++-- src/eatery/migrations/0002_alter_eatery_id.py | 10 +- .../migrations/0003_alter_eatery_image_url.py | 11 +- .../0004_alter_eatery_campus_area.py | 20 ++- .../0005_alter_eatery_campus_area.py | 19 ++- src/eatery/models.py | 3 +- src/eatery/permissions.py | 9 +- src/eatery/serializers.py | 107 +++++++++++-- src/eatery/urls.py | 22 +-- src/eatery/util/constants.py | 15 +- src/eatery/util/convert_from_json.py | 2 +- src/eatery/util/time.py | 1 + src/eatery/views.py | 33 +++-- .../management/commands/populate_models.py | 140 ++++++++++-------- src/eatery_blue_backend/settings.py | 2 - src/event/apps.py | 4 +- src/event/controllers/populate_event.py | 69 +++++---- .../update_models/CornellDiningEvents.py | 8 +- .../schedule/CornellDiningEvents.py | 5 +- .../schedule/RepeatingSchedule.py | 6 +- src/event/datatype/Event.py | 5 +- src/event/datatype/Menu.py | 1 + src/event/datatype/MenuCategory.py | 4 +- src/event/datatype/MenuItem.py | 31 ++-- src/event/datatype/MenuItemSection.py | 5 +- src/event/datatype/MenuSubItem.py | 10 +- src/event/datatype/WaitTime.py | 14 +- src/event/datatype/WaitTimesDay.py | 13 +- src/event/migrations/0001_initial.py | 37 ++++- src/event/migrations/0002_alter_event_id.py | 10 +- .../0003_alter_event_event_description.py | 21 ++- .../0004_alter_event_event_description.py | 21 ++- src/event/models.py | 24 ++- src/event/serializers.py | 7 +- src/event/urls.py | 2 +- src/event/views.py | 3 +- src/item/apps.py | 4 +- src/item/controllers/populate_item.py | 43 +++--- src/item/migrations/0001_initial.py | 19 ++- src/item/migrations/0002_alter_item_id.py | 10 +- src/item/models.py | 9 +- src/item/serializers.py | 12 +- src/item/urls.py | 2 +- src/item/views.py | 3 +- src/manage.py | 3 +- src/person/admin.py | 2 +- src/person/apps.py | 4 +- src/person/migrations/0001_initial.py | 65 ++++++-- src/person/models.py | 21 ++- src/person/serializers.py | 6 +- src/person/urls.py | 2 +- src/person/views.py | 4 +- src/report/apps.py | 4 +- src/report/migrations/0001_initial.py | 28 +++- src/report/migrations/0002_report_netid.py | 6 +- src/report/models.py | 6 +- src/report/serializers.py | 2 +- src/report/urls.py | 2 +- src/util/constants.py | 4 +- 71 files changed, 713 insertions(+), 375 deletions(-) diff --git a/src/category/apps.py b/src/category/apps.py index 1f46433..e953ee6 100644 --- a/src/category/apps.py +++ b/src/category/apps.py @@ -2,5 +2,5 @@ class CategoryConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'category' + default_auto_field = "django.db.models.BigAutoField" + name = "category" diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index 43601a6..e8a23fd 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -21,10 +21,20 @@ def generate_dining_hall_categories(self, json_event, event): """ category_items = {} - category_order = ["Chef's Table", "Chef's Table - Sides", "Grill", "Wok", - "Wok/Asian Station", "Iron Grill", "Mexican Station", "Global", - "Halal", "Kosher Station", "Flat Top Grill"] - + category_order = [ + "Chef's Table", + "Chef's Table - Sides", + "Grill", + "Wok", + "Wok/Asian Station", + "Iron Grill", + "Mexican Station", + "Global", + "Halal", + "Kosher Station", + "Flat Top Grill", + ] + def sort_menu(menu): try: return category_order.index(menu["category"].strip()) @@ -79,7 +89,9 @@ def process(self, events_dict, json_eateries): categories_dict = {} - with open("./static_sources/external_eateries.json", "r") as external_eateries_file: + with open( + "./static_sources/external_eateries.json", "r" + ) as external_eateries_file: external_eateries_json = json.load(external_eateries_file) json_eateries.extend(external_eateries_json["eateries"]) @@ -94,7 +106,7 @@ def process(self, events_dict, json_eateries): continue is_cafe = eatery_is_cafe(json_eatery) - + """ For every event in an eatery --> for every menu in an eatery --> get categories """ diff --git a/src/category/migrations/0001_initial.py b/src/category/migrations/0001_initial.py index bdcbcb3..dca2274 100644 --- a/src/category/migrations/0001_initial.py +++ b/src/category/migrations/0001_initial.py @@ -9,16 +9,23 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('event', '0001_initial'), + ("event", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Category', + name="Category", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('category', models.CharField(default='General', max_length=40)), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='menu', to='event.event')), + ("id", models.AutoField(primary_key=True, serialize=False)), + ("category", models.CharField(default="General", max_length=40)), + ( + "event", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="menu", + to="event.event", + ), + ), ], ), ] diff --git a/src/category/migrations/0002_alter_category_id.py b/src/category/migrations/0002_alter_category_id.py index 410927c..cbb43d1 100644 --- a/src/category/migrations/0002_alter_category_id.py +++ b/src/category/migrations/0002_alter_category_id.py @@ -6,13 +6,15 @@ class Migration(migrations.Migration): dependencies = [ - ('category', '0001_initial'), + ("category", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='category', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + model_name="category", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), ), ] diff --git a/src/category/models.py b/src/category/models.py index 01b85aa..9538a42 100644 --- a/src/category/models.py +++ b/src/category/models.py @@ -5,6 +5,6 @@ class Category(models.Model): event = models.ForeignKey(Event, related_name="menu", on_delete=models.DO_NOTHING) category = models.CharField(max_length=40, default="General") - + def __str__(self): return self.category diff --git a/src/category/serializers.py b/src/category/serializers.py index 6af5027..74fc181 100644 --- a/src/category/serializers.py +++ b/src/category/serializers.py @@ -16,6 +16,7 @@ class Meta: model = Category fields = ["id", "category", "event", "items"] + class CategorySerializerOptimized(serializers.ModelSerializer): items = ItemSerializerOptimized(many=True, read_only=True) diff --git a/src/category/urls.py b/src/category/urls.py index d32462d..e245c26 100644 --- a/src/category/urls.py +++ b/src/category/urls.py @@ -7,4 +7,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/category/views.py b/src/category/views.py index a95917c..bda65d2 100644 --- a/src/category/views.py +++ b/src/category/views.py @@ -2,6 +2,7 @@ from category.models import Category from category.serializers import CategorySerializer + class CategoryViewSet(viewsets.ModelViewSet): queryset = Category.objects.all() - serializer_class = CategorySerializer \ No newline at end of file + serializer_class = CategorySerializer diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index f3f6634..3961d7d 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -4,6 +4,7 @@ from eatery.models import Eatery from django.core.exceptions import ObjectDoesNotExist + class PopulateEateryController: def __init__(self): self = self @@ -66,7 +67,7 @@ def add_eatery_store(self): for line in file: if len(line) > 2: json_objs.append(json.loads(line)) - + for json_obj in json_objs: try: object = Eatery.objects.get(id=int(json_obj["id"])) diff --git a/src/eatery/controllers/update_eatery.py b/src/eatery/controllers/update_eatery.py index be31278..7bb3752 100644 --- a/src/eatery/controllers/update_eatery.py +++ b/src/eatery/controllers/update_eatery.py @@ -72,6 +72,7 @@ def upload_image(self, image): >> left merge Eatery and CornellDiningNow >> left merge Events and CornellDiningNow """ + def compare(self): pass diff --git a/src/eatery/datatype/Eatery.py b/src/eatery/datatype/Eatery.py index b0a80bd..20f1413 100644 --- a/src/eatery/datatype/Eatery.py +++ b/src/eatery/datatype/Eatery.py @@ -3,9 +3,10 @@ from typing import Optional import pytz -#from event.datatype.Event import Event, filter_range -#from api.datatype.WaitTimesDay import WaitTimesDay -#from eatery.datatype.EateryAlert import EateryAlert + +# from event.datatype.Event import Event, filter_range +# from api.datatype.WaitTimesDay import WaitTimesDay +# from eatery.datatype.EateryAlert import EateryAlert class EateryID(Enum): @@ -49,4 +50,3 @@ class EateryID(Enum): MORRISON_DINING = 39 NOVICKS_CAFE = 40 VET_CAFE = 41 - diff --git a/src/eatery/datatype/EateryAlert.py b/src/eatery/datatype/EateryAlert.py index 2bb511b..ffd307b 100644 --- a/src/eatery/datatype/EateryAlert.py +++ b/src/eatery/datatype/EateryAlert.py @@ -1,11 +1,7 @@ class EateryAlert: def __init__( - self, - id: int, - description: str, - start_timestamp: int, - end_timestamp: int + self, id: int, description: str, start_timestamp: int, end_timestamp: int ): self.id = id self.description = description @@ -17,14 +13,14 @@ def to_json(self): "id": 1, "description": self.description, "start_timestamp": self.start_timestamp, - "end_timestamp": self.end_timestamp + "end_timestamp": self.end_timestamp, } @staticmethod def from_json(alert_json): return EateryAlert( - id = alert_json["id"], + id=alert_json["id"], description=alert_json["description"], start_timestamp=alert_json["start_timestamp"], - end_timestamp=alert_json["end_timestamp"] + end_timestamp=alert_json["end_timestamp"], ) diff --git a/src/eatery/migrations/0001_initial.py b/src/eatery/migrations/0001_initial.py index ca330b0..87246d5 100644 --- a/src/eatery/migrations/0001_initial.py +++ b/src/eatery/migrations/0001_initial.py @@ -7,25 +7,41 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Eatery', + name="Eatery", fields=[ - ('id', models.IntegerField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=40)), - ('menu_summary', models.TextField(blank=True, default='', null=True)), - ('image_url', models.URLField(blank=True)), - ('location', models.TextField(blank=True)), - ('campus_area', models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15)), - ('online_order_url', models.URLField(blank=True, null=True)), - ('latitude', models.FloatField(blank=True, null=True)), - ('longitude', models.FloatField(blank=True, null=True)), - ('payment_accepts_meal_swipes', models.BooleanField(blank=True, null=True)), - ('payment_accepts_brbs', models.BooleanField(blank=True, null=True)), - ('payment_accepts_cash', models.BooleanField(blank=True, null=True)), + ("id", models.IntegerField(primary_key=True, serialize=False)), + ("name", models.CharField(max_length=40)), + ("menu_summary", models.TextField(blank=True, default="", null=True)), + ("image_url", models.URLField(blank=True)), + ("location", models.TextField(blank=True)), + ( + "campus_area", + models.CharField( + blank=True, + choices=[ + ("West", "West"), + ("North", "North"), + ("Central", "Central"), + ("Collegetown", "Collegetown"), + ("", "None"), + ], + default="", + max_length=15, + ), + ), + ("online_order_url", models.URLField(blank=True, null=True)), + ("latitude", models.FloatField(blank=True, null=True)), + ("longitude", models.FloatField(blank=True, null=True)), + ( + "payment_accepts_meal_swipes", + models.BooleanField(blank=True, null=True), + ), + ("payment_accepts_brbs", models.BooleanField(blank=True, null=True)), + ("payment_accepts_cash", models.BooleanField(blank=True, null=True)), ], ), ] diff --git a/src/eatery/migrations/0002_alter_eatery_id.py b/src/eatery/migrations/0002_alter_eatery_id.py index 714af0b..2b70b83 100644 --- a/src/eatery/migrations/0002_alter_eatery_id.py +++ b/src/eatery/migrations/0002_alter_eatery_id.py @@ -6,13 +6,15 @@ class Migration(migrations.Migration): dependencies = [ - ('eatery', '0001_initial'), + ("eatery", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='eatery', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + model_name="eatery", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), ), ] diff --git a/src/eatery/migrations/0003_alter_eatery_image_url.py b/src/eatery/migrations/0003_alter_eatery_image_url.py index f5e1c81..c276bb6 100644 --- a/src/eatery/migrations/0003_alter_eatery_image_url.py +++ b/src/eatery/migrations/0003_alter_eatery_image_url.py @@ -6,13 +6,16 @@ class Migration(migrations.Migration): dependencies = [ - ('eatery', '0002_alter_eatery_id'), + ("eatery", "0002_alter_eatery_id"), ] operations = [ migrations.AlterField( - model_name='eatery', - name='image_url', - field=models.URLField(blank=True, default='https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg'), + model_name="eatery", + name="image_url", + field=models.URLField( + blank=True, + default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg", + ), ), ] diff --git a/src/eatery/migrations/0004_alter_eatery_campus_area.py b/src/eatery/migrations/0004_alter_eatery_campus_area.py index c83d70d..77d43bd 100644 --- a/src/eatery/migrations/0004_alter_eatery_campus_area.py +++ b/src/eatery/migrations/0004_alter_eatery_campus_area.py @@ -6,13 +6,25 @@ class Migration(migrations.Migration): dependencies = [ - ('eatery', '0003_alter_eatery_image_url'), + ("eatery", "0003_alter_eatery_image_url"), ] operations = [ migrations.AlterField( - model_name='eatery', - name='campus_area', - field=models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('East', 'East'), ('', 'None')], default='', max_length=15), + model_name="eatery", + name="campus_area", + field=models.CharField( + blank=True, + choices=[ + ("West", "West"), + ("North", "North"), + ("Central", "Central"), + ("Collegetown", "Collegetown"), + ("East", "East"), + ("", "None"), + ], + default="", + max_length=15, + ), ), ] diff --git a/src/eatery/migrations/0005_alter_eatery_campus_area.py b/src/eatery/migrations/0005_alter_eatery_campus_area.py index 55187b0..df3bfb1 100644 --- a/src/eatery/migrations/0005_alter_eatery_campus_area.py +++ b/src/eatery/migrations/0005_alter_eatery_campus_area.py @@ -6,13 +6,24 @@ class Migration(migrations.Migration): dependencies = [ - ('eatery', '0004_alter_eatery_campus_area'), + ("eatery", "0004_alter_eatery_campus_area"), ] operations = [ migrations.AlterField( - model_name='eatery', - name='campus_area', - field=models.CharField(blank=True, choices=[('West', 'West'), ('North', 'North'), ('Central', 'Central'), ('Collegetown', 'Collegetown'), ('', 'None')], default='', max_length=15), + model_name="eatery", + name="campus_area", + field=models.CharField( + blank=True, + choices=[ + ("West", "West"), + ("North", "North"), + ("Central", "Central"), + ("Collegetown", "Collegetown"), + ("", "None"), + ], + default="", + max_length=15, + ), ), ] diff --git a/src/eatery/models.py b/src/eatery/models.py index 731858a..6f4ea1e 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -2,6 +2,7 @@ from django.db import connection from eatery.util.constants import DEFAULT_IMAGE_URL + class Eatery(models.Model): class CampusArea(models.TextChoices): WEST = "West" @@ -23,6 +24,6 @@ class CampusArea(models.TextChoices): payment_accepts_meal_swipes = models.BooleanField(null=True, blank=True) payment_accepts_brbs = models.BooleanField(null=True, blank=True) payment_accepts_cash = models.BooleanField(null=True, blank=True) - + def __str__(self): return self.name diff --git a/src/eatery/permissions.py b/src/eatery/permissions.py index 42af072..80a9513 100644 --- a/src/eatery/permissions.py +++ b/src/eatery/permissions.py @@ -1,13 +1,14 @@ from rest_framework import permissions + class EateryPermission(permissions.BasePermission): def has_permission(self, request, view): - if view.action in ['list', 'retrieve']: + if view.action in ["list", "retrieve"]: return True return request.user.is_staff - + def has_object_permission(self, request, view, obj): - if view.action in ['retrieve']: + if view.action in ["retrieve"]: return True - return request.user.is_staff \ No newline at end of file + return request.user.is_staff diff --git a/src/eatery/serializers.py b/src/eatery/serializers.py index dbe4c0d..9f6dae0 100644 --- a/src/eatery/serializers.py +++ b/src/eatery/serializers.py @@ -1,15 +1,23 @@ from rest_framework import serializers from eatery.models import Eatery from event.models import Event -from event.serializers import EventSerializer, EventSerializerSimple, EventSerializerOptimized +from event.serializers import ( + EventSerializer, + EventSerializerSimple, + EventSerializerOptimized, +) from datetime import timedelta, datetime from zoneinfo import ZoneInfo + class EaterySerializer(serializers.ModelSerializer): id = serializers.IntegerField() name = serializers.CharField() - menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") - image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + menu_summary = serializers.CharField(allow_null=True, default="Cornell Eatery") + image_url = serializers.URLField( + allow_null=True, + default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg", + ) location = serializers.CharField(allow_null=True) campus_area = serializers.CharField(allow_null=True) online_order_url = serializers.URLField(allow_null=True) @@ -24,48 +32,115 @@ class EaterySerializer(serializers.ModelSerializer): def create(self, validated_data): eatery, _ = Eatery.objects.get_or_create(**validated_data) return eatery - + class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] - + fields = [ + "id", + "name", + "menu_summary", + "image_url", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + "events", + ] + + class EaterySerializerOptimized(serializers.ModelSerializer): events = EventSerializerOptimized(many=True, read_only=True) class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + fields = [ + "id", + "name", + "menu_summary", + "image_url", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + "events", + ] class EaterySerializerSimple(serializers.ModelSerializer): - menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") - image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + menu_summary = serializers.CharField(allow_null=True, default="Cornell Eatery") + image_url = serializers.URLField( + allow_null=True, + default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg", + ) events = EventSerializerSimple(many=True, read_only=True) class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] + fields = [ + "id", + "name", + "menu_summary", + "image_url", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + "events", + ] + class EaterySerializerByDay(serializers.ModelSerializer): - menu_summary = serializers.CharField(allow_null=True,default="Cornell Eatery") - image_url = serializers.URLField(allow_null=True,default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg") + menu_summary = serializers.CharField(allow_null=True, default="Cornell Eatery") + image_url = serializers.URLField( + allow_null=True, + default="https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg", + ) events = serializers.SerializerMethodField() def get_events(self, obj): day_offset = self.context.get("day") now = datetime.now(ZoneInfo("America/New_York")) - day = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=day_offset) + day = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta( + days=day_offset + ) day_unix = int(day.timestamp()) day_end_unix = int((day + timedelta(days=1)).timestamp()) print(f"Now: {now}") print(f"Day: {day}") print(f"Day Unix: {day_unix}") print(f"Day End Unix: {day_end_unix}") - events = Event.objects.filter(eatery=obj.id, start__gte=day_unix, start__lt=day_end_unix) + events = Event.objects.filter( + eatery=obj.id, start__gte=day_unix, start__lt=day_end_unix + ) serializer = EventSerializerOptimized(instance=events, many=True) return serializer.data class Meta: model = Eatery - fields = ['id', 'name', 'menu_summary', 'image_url', 'location', 'campus_area', 'online_order_url', 'latitude', 'longitude', 'payment_accepts_meal_swipes', 'payment_accepts_brbs', 'payment_accepts_cash', 'events'] - + fields = [ + "id", + "name", + "menu_summary", + "image_url", + "location", + "campus_area", + "online_order_url", + "latitude", + "longitude", + "payment_accepts_meal_swipes", + "payment_accepts_brbs", + "payment_accepts_cash", + "events", + ] diff --git a/src/eatery/urls.py b/src/eatery/urls.py index e4519cf..5bed8fd 100644 --- a/src/eatery/urls.py +++ b/src/eatery/urls.py @@ -1,21 +1,15 @@ from django.urls import path from eatery.views import EateryViewSet, GetEateriesSimple, GetEateriesByDay -eateries_list = EateryViewSet.as_view({ - 'get':'list', - 'post': 'create' -}) +eateries_list = EateryViewSet.as_view({"get": "list", "post": "create"}) -eatery_list = EateryViewSet.as_view({ - 'get':'retrieve', - 'put':'update', - 'patch':'partial_update', - 'delete':'destroy' -}) +eatery_list = EateryViewSet.as_view( + {"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"} +) urlpatterns = [ - path("", eateries_list, name='eateries-list'), - path("/", eatery_list, name='eatery-list'), - path("simple/", GetEateriesSimple.as_view(), name='eateries-simple'), + path("", eateries_list, name="eateries-list"), + path("/", eatery_list, name="eatery-list"), + path("simple/", GetEateriesSimple.as_view(), name="eateries-simple"), path("day//", GetEateriesByDay.as_view(), name="eateries-day"), -] \ No newline at end of file +] diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index 76e95ff..e9690e7 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -3,12 +3,23 @@ from eatery.datatype.Eatery import EateryID CORNELL_DINING_URL = "https://now.dining.cornell.edu/api/1.0/dining/eateries.json" -CORNELL_VENDOR_URL = "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" +CORNELL_VENDOR_URL = ( + "https://vendor-api-extra.scl.cornell.edu/api/external/location-count" +) -DAY_OF_WEEK_LIST = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] +DAY_OF_WEEK_LIST = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", +] DEFAULT_IMAGE_URL = "https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg" + class SnapshotFileName(Enum): EATERY_STORE = "eatery_store.txt" ALERT_STORE = "alert_store.txt" diff --git a/src/eatery/util/convert_from_json.py b/src/eatery/util/convert_from_json.py index 9134b41..7fca648 100644 --- a/src/eatery/util/convert_from_json.py +++ b/src/eatery/util/convert_from_json.py @@ -22,4 +22,4 @@ def from_json(obj: Union[list, dict], *args, **kwargs): return Event.from_json(obj) def description(self): - return "ConvertFromJson""" \ No newline at end of file + return "ConvertFromJson""" diff --git a/src/eatery/util/time.py b/src/eatery/util/time.py index 7fce11d..b03a0dd 100644 --- a/src/eatery/util/time.py +++ b/src/eatery/util/time.py @@ -1,5 +1,6 @@ from datetime import date, time, datetime import pytz + def combined_timestamp(date: date, time: time, tzinfo: pytz.timezone) -> int: return int(tzinfo.localize(datetime.combine(date, time)).timestamp()) diff --git a/src/eatery/views.py b/src/eatery/views.py index db04c35..85fd639 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -1,4 +1,9 @@ -from eatery.serializers import EaterySerializer, EaterySerializerSimple, EaterySerializerByDay, EaterySerializerOptimized +from eatery.serializers import ( + EaterySerializer, + EaterySerializerSimple, + EaterySerializerByDay, + EaterySerializerOptimized, +) from eatery.util.json import FieldType, error_json, success_json, verify_json_fields from django.http import JsonResponse from django.shortcuts import get_object_or_404 @@ -12,10 +17,12 @@ from eatery.models import Eatery from .controllers.update_eatery import UpdateEateryController + class EateryViewSet(viewsets.ModelViewSet): """ View and edit eateries (all, or specific) """ + queryset = Eatery.objects.all() serializer_class = EaterySerializer permission_classes = [EateryPermission] @@ -25,7 +32,7 @@ def retrieve(self, request, *args, **kwargs): serializer = EaterySerializerOptimized(instance) return Response(serializer.data) - @method_decorator(cache_page(60*60*2)) # cache for 2 hours + @method_decorator(cache_page(60 * 60 * 2)) # cache for 2 hours def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) serializer = EaterySerializerOptimized(queryset, many=True) @@ -36,10 +43,10 @@ def get_object(self): lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field assert lookup_url_kwarg in self.kwargs, ( - 'Expected view %s to be called with a URL keyword argument ' + "Expected view %s to be called with a URL keyword argument " 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - 'attribute on the view correctly.' % - (self.__class__.__name__, lookup_url_kwarg) + "attribute on the view correctly." + % (self.__class__.__name__, lookup_url_kwarg) ) # Uses the lookup_field attribute, which defaults to `pk` filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} @@ -84,19 +91,27 @@ def update(self, request, *args, **kwargs): except Exception as e: return JsonResponse(error_json(str(e))) + class GetEateriesSimple(APIView): """ View all eateries with less information """ + def get(self, request): eateries = EaterySerializerSimple(Eatery.objects.all(), many=True) return Response(eateries.data) - + + class GetEateriesByDay(APIView): """ Get all eatery information by day """ - @method_decorator(cache_page(60*60*2)) # cache for 2 hours + + @method_decorator(cache_page(60 * 60 * 2)) # cache for 2 hours def get(self, request, day): - eateries = EaterySerializerByDay(Eatery.objects.exclude(events__event_description="Open"), many=True, context={"day": day}) - return Response(eateries.data) \ No newline at end of file + eateries = EaterySerializerByDay( + Eatery.objects.exclude(events__event_description="Open"), + many=True, + context={"day": day}, + ) + return Response(eateries.data) diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index 4191bd0..952a733 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -1,6 +1,6 @@ from django.core.management.base import BaseCommand from datetime import datetime -import requests +import requests from eatery.util.constants import CORNELL_DINING_URL from event.models import Event from eatery.controllers.populate_eatery import PopulateEateryController @@ -8,65 +8,81 @@ from item.controllers.populate_item import PopulateItemController from category.controllers.populate_category import PopulateCategoryController + class Command(BaseCommand): - help = 'Populates all models' - def handle(self, *args, **kwargs): - self.stdout.write(f"Populating models at {datetime.now()} UTC") - start = int(datetime.now().timestamp()) - self.process() - self.stdout.write(f"Finished populating models at {datetime.now()} UTC ({int(datetime.now().timestamp()) - start}s)") - - def get_json(self): - try: - response = requests.get(CORNELL_DINING_URL) - except Exception as e: - raise e - if response.status_code <= 400: - response = response.json() - json_eateries = response["data"]["eateries"] - return json_eateries - - def logger_wrapper(self, command_obj, log_title, args): - pre = int(datetime.now().timestamp()) - print(f"{datetime.now()} UTC: {log_title}") - output = command_obj.process(*args) - print(f"Done ({int(datetime.now().timestamp()) - pre}s) ") - return output - - def process(self): - """ - 1. Get JSON from API - - 2. create eateries (fron CDN json) - - 3. create events (from CDN json) - return events_dict = { eatery_id : [event, event, event...], eatery_id : ... } - - 4. create menus for every eatery's events - return menus_dict = { eatery_id : [menu, menu, menu...] } - - 5. create categories in each menu - return categories_dict = - { eatery_id : - { menu[i] : {"category_name" : id, "category_name" : id...}, - menu[i] : {"category_name" : id...} - } - } - - 6. create items for each category - - """ - - json_eateries = self.get_json() - - Event.truncate() - - self.logger_wrapper(PopulateEateryController(), "Populating eateries", [json_eateries]) - - events_dict = self.logger_wrapper(PopulateEventController(), "Populating events", [json_eateries]) - - categories_dict = self.logger_wrapper(PopulateCategoryController(), "Populating categories", [events_dict, json_eateries]) - - self.logger_wrapper(PopulateItemController(), "Populating items", [categories_dict, json_eateries]) - - print("Done populating") + help = "Populates all models" + + def handle(self, *args, **kwargs): + self.stdout.write(f"Populating models at {datetime.now()} UTC") + start = int(datetime.now().timestamp()) + self.process() + self.stdout.write( + f"Finished populating models at {datetime.now()} UTC ({int(datetime.now().timestamp()) - start}s)" + ) + + def get_json(self): + try: + response = requests.get(CORNELL_DINING_URL) + except Exception as e: + raise e + if response.status_code <= 400: + response = response.json() + json_eateries = response["data"]["eateries"] + return json_eateries + + def logger_wrapper(self, command_obj, log_title, args): + pre = int(datetime.now().timestamp()) + print(f"{datetime.now()} UTC: {log_title}") + output = command_obj.process(*args) + print(f"Done ({int(datetime.now().timestamp()) - pre}s) ") + return output + + def process(self): + """ + 1. Get JSON from API + + 2. create eateries (fron CDN json) + + 3. create events (from CDN json) + return events_dict = { eatery_id : [event, event, event...], eatery_id : ... } + + 4. create menus for every eatery's events + return menus_dict = { eatery_id : [menu, menu, menu...] } + + 5. create categories in each menu + return categories_dict = + { eatery_id : + { menu[i] : {"category_name" : id, "category_name" : id...}, + menu[i] : {"category_name" : id...} + } + } + + 6. create items for each category + + """ + + json_eateries = self.get_json() + + Event.truncate() + + self.logger_wrapper( + PopulateEateryController(), "Populating eateries", [json_eateries] + ) + + events_dict = self.logger_wrapper( + PopulateEventController(), "Populating events", [json_eateries] + ) + + categories_dict = self.logger_wrapper( + PopulateCategoryController(), + "Populating categories", + [events_dict, json_eateries], + ) + + self.logger_wrapper( + PopulateItemController(), + "Populating items", + [categories_dict, json_eateries], + ) + + print("Done populating") diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 53dbae8..3101d88 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -40,7 +40,6 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework.authtoken", - # Apps "eatery_blue_backend", "eatery", @@ -49,7 +48,6 @@ "item", "category", "person", - # Third party "rest_framework", ] diff --git a/src/event/apps.py b/src/event/apps.py index 766f251..52dbc82 100644 --- a/src/event/apps.py +++ b/src/event/apps.py @@ -1,6 +1,6 @@ from django.apps import AppConfig + class EventConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' + default_auto_field = "django.db.models.BigAutoField" name = "event" - diff --git a/src/event/controllers/populate_event.py b/src/event/controllers/populate_event.py index 8823920..b98a25e 100644 --- a/src/event/controllers/populate_event.py +++ b/src/event/controllers/populate_event.py @@ -4,7 +4,8 @@ import json import pytz -class PopulateEventController(): + +class PopulateEventController: def __init__(self): self = self @@ -13,33 +14,34 @@ def generate_events(self, json_eatery): From an eatery json from CDN, create events for that eatery and add to event model. """ - #events = [ event obj, event, event ... ] for an eatery. + # events = [ event obj, event, event ... ] for an eatery. events = [] json_dates = json_eatery["operatingHours"] for json_date in json_dates: json_events = json_date["events"] - + for json_event in json_events: # Create an event: eatery_id = dining_id_to_internal_id(json_eatery["id"]).value data = { - 'eatery': eatery_id, - 'event_description': json_event["descr"], - 'start' : int(json_event["startTimestamp"]), - 'end' : int(json_event["endTimestamp"])} + "eatery": eatery_id, + "event_description": json_event["descr"], + "start": int(json_event["startTimestamp"]), + "end": int(json_event["endTimestamp"]), + } event = EventSerializer(data=data) - + if event.is_valid(): event.save() else: print(event.errors) return event.errors - - events.append(event.data["id"]) + + events.append(event.data["id"]) return events - + def generate_external_events(self, json_eatery): json_dates = json_eatery["operatingHours"] events = [] @@ -48,47 +50,60 @@ def generate_external_events(self, json_eatery): date = datetime.now() while date.strftime("%A").lower() != json_date["weekday"].lower(): date += timedelta(days=1) - start_string = json_event['start'] - timezone = pytz.timezone('US/Eastern') - start_time = datetime(date.year, date.month, date.day, int(start_string[:2]), int(start_string[3:])) + start_string = json_event["start"] + timezone = pytz.timezone("US/Eastern") + start_time = datetime( + date.year, + date.month, + date.day, + int(start_string[:2]), + int(start_string[3:]), + ) start_timestamp = timezone.localize(start_time).timestamp() - end_string = json_event['end'] + end_string = json_event["end"] if int(end_string[:2]) < int(start_string[:2]): date += timedelta(days=1) - end_time = datetime(date.year, date.month, date.day, int(end_string[:2]), int(end_string[3:])) + end_time = datetime( + date.year, + date.month, + date.day, + int(end_string[:2]), + int(end_string[3:]), + ) end_timestamp = timezone.localize(end_time).timestamp() eatery_id = json_eatery["id"] data = { - 'eatery': eatery_id, - 'event_description': json_event["descr"], - 'start' : start_timestamp, - 'end' : end_timestamp} + "eatery": eatery_id, + "event_description": json_event["descr"], + "start": start_timestamp, + "end": end_timestamp, + } event = EventSerializer(data=data) - + if event.is_valid(): event.save() else: print(event.errors) return event.errors - + events.append(event.data["id"]) return events def process(self, json_eateries): - #events_dict { eatery_id : [event, event, event...], eatery_id : ... } + # events_dict { eatery_id : [event, event, event...], eatery_id : ... } events_dict = {} for json_eatery in json_eateries: eatery_id = int(json_eatery["id"]) events = self.generate_events(json_eatery) - events_dict[eatery_id] = events + events_dict[eatery_id] = events # create custom events for external eateries with open("./static_sources/external_eateries.json", "r") as file: json_obj = json.load(file) - for eatery in json_obj['eateries']: - events_dict[eatery['id']] = self.generate_external_events(eatery) + for eatery in json_obj["eateries"]: + events_dict[eatery["id"]] = self.generate_external_events(eatery) - return events_dict + return events_dict diff --git a/src/event/controllers/update_models/CornellDiningEvents.py b/src/event/controllers/update_models/CornellDiningEvents.py index 2ae6387..40301b3 100644 --- a/src/event/controllers/update_models/CornellDiningEvents.py +++ b/src/event/controllers/update_models/CornellDiningEvents.py @@ -48,7 +48,8 @@ def parse_eatery(json_eatery: dict) -> Eatery: @staticmethod def eatery_events_from_json( - json_operating_hours: list, json_dining_items: list, is_cafe: bool) -> list[Event]: + json_operating_hours: list, json_dining_items: list, is_cafe: bool + ) -> list[Event]: json_operating_hours = sorted( json_operating_hours, key=lambda json_date_events: json_date_events["date"] ) @@ -114,10 +115,7 @@ def dining_hall_menu_from_json(json_menu: list) -> Menu: @staticmethod def from_cornell_dining_json(json_item: dict): - return MenuItem( - healthy=json_item["healthy"], - name=json_item["item"] - ) + return MenuItem(healthy=json_item["healthy"], name=json_item["item"]) def description(self): return "CornellDiningEvents" diff --git a/src/event/controllers/update_models/schedule/CornellDiningEvents.py b/src/event/controllers/update_models/schedule/CornellDiningEvents.py index 7101169..40301b3 100644 --- a/src/event/controllers/update_models/schedule/CornellDiningEvents.py +++ b/src/event/controllers/update_models/schedule/CornellDiningEvents.py @@ -115,10 +115,7 @@ def dining_hall_menu_from_json(json_menu: list) -> Menu: @staticmethod def from_cornell_dining_json(json_item: dict): - return MenuItem( - healthy=json_item["healthy"], - name=json_item["item"] - ) + return MenuItem(healthy=json_item["healthy"], name=json_item["item"]) def description(self): return "CornellDiningEvents" diff --git a/src/event/controllers/update_models/schedule/RepeatingSchedule.py b/src/event/controllers/update_models/schedule/RepeatingSchedule.py index 6b27a8a..58c5d10 100644 --- a/src/event/controllers/update_models/schedule/RepeatingSchedule.py +++ b/src/event/controllers/update_models/schedule/RepeatingSchedule.py @@ -14,9 +14,9 @@ def __init__(self, eatery_id: EateryID, cache): def __call__(self, *args, **kwargs) -> list[Eatery]: if "day_of_week_schedules" not in self.cache: - self.cache[ - "day_of_week_schedules" - ] = RepeatingEventSchedule.objects.all().values() + self.cache["day_of_week_schedules"] = ( + RepeatingEventSchedule.objects.all().values() + ) repeating_schedules = [ sched for sched in self.cache["day_of_week_schedules"] diff --git a/src/event/datatype/Event.py b/src/event/datatype/Event.py index 02e8024..79bb19d 100644 --- a/src/event/datatype/Event.py +++ b/src/event/datatype/Event.py @@ -3,7 +3,8 @@ import pytz from datatype.Menu import Menu -#from api.util.time import combined_timestamp + +# from api.util.time import combined_timestamp class Event: @@ -114,4 +115,4 @@ def filter_range( else: raise Exception( f"Improper arguments. tzinfo={tzinfo}, start={start}, end={end}" - ) \ No newline at end of file + ) diff --git a/src/event/datatype/Menu.py b/src/event/datatype/Menu.py index 118d378..6e77a77 100644 --- a/src/event/datatype/Menu.py +++ b/src/event/datatype/Menu.py @@ -1,5 +1,6 @@ from datatype.MenuCategory import MenuCategory + class Menu: def __init__(self, categories: list[MenuCategory]): diff --git a/src/event/datatype/MenuCategory.py b/src/event/datatype/MenuCategory.py index f2cd1cc..e54262a 100644 --- a/src/event/datatype/MenuCategory.py +++ b/src/event/datatype/MenuCategory.py @@ -10,12 +10,12 @@ def __init__(self, category: str, items: list[MenuItem]): def to_json(self): return { "category": self.category, - "items": [item.to_json() for item in self.items] + "items": [item.to_json() for item in self.items], } @staticmethod def from_json(category_json): return MenuCategory( category=category_json["category"], - items=[MenuItem.from_json(item) for item in category_json["items"]] + items=[MenuItem.from_json(item) for item in category_json["items"]], ) diff --git a/src/event/datatype/MenuItem.py b/src/event/datatype/MenuItem.py index 34124db..72b8b33 100644 --- a/src/event/datatype/MenuItem.py +++ b/src/event/datatype/MenuItem.py @@ -2,15 +2,16 @@ from api.datatype.MenuItemSection import MenuItemSection + class MenuItem: def __init__( - self, - name: str, - healthy: Optional[bool] = None, - base_price: Optional[float] = None, - description: Optional[str] = None, - sections: Optional[MenuItemSection] = None + self, + name: str, + healthy: Optional[bool] = None, + base_price: Optional[float] = None, + description: Optional[str] = None, + sections: Optional[MenuItemSection] = None, ): self.healthy = healthy self.name = name @@ -24,7 +25,11 @@ def to_json(self): "name": self.name, "base_price": self.base_price, "description": self.description, - "sections": None if self.sections is None else [section.to_json() for section in self.sections] + "sections": ( + None + if self.sections is None + else [section.to_json() for section in self.sections] + ), } @staticmethod @@ -34,6 +39,12 @@ def from_json(item_json): healthy=item_json["healthy"], base_price=item_json["base_price"], description=item_json["description"], - sections=None if "sections" not in item_json or item_json["sections"] is None - else [MenuItemSection.from_json(section) for section in item_json["sections"]] - ) + sections=( + None + if "sections" not in item_json or item_json["sections"] is None + else [ + MenuItemSection.from_json(section) + for section in item_json["sections"] + ] + ), + ) diff --git a/src/event/datatype/MenuItemSection.py b/src/event/datatype/MenuItemSection.py index 770c376..1ac44af 100644 --- a/src/event/datatype/MenuItemSection.py +++ b/src/event/datatype/MenuItemSection.py @@ -1,5 +1,6 @@ from api.datatype.MenuSubItem import MenuSubItem + class MenuItemSection: def __init__(self, name: str, subitems: list[MenuSubItem]): @@ -9,12 +10,12 @@ def __init__(self, name: str, subitems: list[MenuSubItem]): def to_json(self): return { "name": self.name, - "subitems": [item.to_json() for item in self.subitems] + "subitems": [item.to_json() for item in self.subitems], } @staticmethod def from_json(section_json): return MenuItemSection( name=section_json["name"], - subitems=[MenuSubItem.from_json(item) for item in section_json["subitems"]] + subitems=[MenuSubItem.from_json(item) for item in section_json["subitems"]], ) diff --git a/src/event/datatype/MenuSubItem.py b/src/event/datatype/MenuSubItem.py index 854b0ed..7c82c28 100644 --- a/src/event/datatype/MenuSubItem.py +++ b/src/event/datatype/MenuSubItem.py @@ -1,12 +1,10 @@ from typing import Optional + class MenuSubItem: def __init__( - self, - name: str, - total_price: Optional[float], - additional_price: Optional[float] + self, name: str, total_price: Optional[float], additional_price: Optional[float] ): self.name = name self.total_price = total_price @@ -16,7 +14,7 @@ def to_json(self): return { "name": self.name, "total_price": self.total_price, - "additional_price": self.additional_price + "additional_price": self.additional_price, } @staticmethod @@ -24,5 +22,5 @@ def from_json(item_json): return MenuSubItem( name=item_json["name"], total_price=item_json.get("total_price"), - additional_price=item_json.get("additional_price") + additional_price=item_json.get("additional_price"), ) diff --git a/src/event/datatype/WaitTime.py b/src/event/datatype/WaitTime.py index 7e5cfca..70c8265 100644 --- a/src/event/datatype/WaitTime.py +++ b/src/event/datatype/WaitTime.py @@ -1,10 +1,10 @@ class WaitTime: def __init__( - self, - timestamp: int, - wait_time_low: float, - wait_time_expected: float, - wait_time_high: float + self, + timestamp: int, + wait_time_low: float, + wait_time_expected: float, + wait_time_high: float, ): self.timestamp = timestamp self.wait_time_low = wait_time_low @@ -16,7 +16,7 @@ def to_json(self): "timestamp": self.timestamp, "wait_time_low": self.wait_time_low, "wait_time_expected": self.wait_time_expected, - "wait_time_high": self.wait_time_high + "wait_time_high": self.wait_time_high, } @staticmethod @@ -25,5 +25,5 @@ def from_json(wait_time_json): timestamp=wait_time_json["timestamp"], wait_time_low=wait_time_json["wait_time_low"], wait_time_expected=wait_time_json["wait_time_expected"], - wait_time_high=wait_time_json["wait_time_high"] + wait_time_high=wait_time_json["wait_time_high"], ) diff --git a/src/event/datatype/WaitTimesDay.py b/src/event/datatype/WaitTimesDay.py index cb9ea86..e3c724c 100644 --- a/src/event/datatype/WaitTimesDay.py +++ b/src/event/datatype/WaitTimesDay.py @@ -3,23 +3,22 @@ class WaitTimesDay: - def __init__( - self, - canonical_date: date, - data: list[WaitTime] - ): + def __init__(self, canonical_date: date, data: list[WaitTime]): self.canonical_date = canonical_date self.data = data def to_json(self): return { "canonical_date": str(self.canonical_date), - "data": [wait_time.to_json() for wait_time in self.data] + "data": [wait_time.to_json() for wait_time in self.data], } @staticmethod def from_json(wait_times_day_json): return WaitTimesDay( canonical_date=date.fromisoformat(wait_times_day_json["canonical_date"]), - data=[WaitTime.from_json(wait_time) for wait_time in wait_times_day_json["data"]] + data=[ + WaitTime.from_json(wait_time) + for wait_time in wait_times_day_json["data"] + ], ) diff --git a/src/event/migrations/0001_initial.py b/src/event/migrations/0001_initial.py index fc114f2..e5a3879 100644 --- a/src/event/migrations/0001_initial.py +++ b/src/event/migrations/0001_initial.py @@ -9,18 +9,41 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('eatery', '0001_initial'), + ("eatery", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Event', + name="Event", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('event_description', models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', null=True)), - ('start', models.IntegerField(default=0)), - ('end', models.IntegerField(default=0)), - ('eatery', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='events', to='eatery.eatery')), + ("id", models.AutoField(primary_key=True, serialize=False)), + ( + "event_description", + models.TextField( + blank=True, + choices=[ + ("Breakfast", "Breakfast"), + ("Brunch", "Brunch"), + ("Lunch", "Lunch"), + ("Dinner", "Dinner"), + ("General", "General"), + ("Cafe", "Cafe"), + ("Pants", "Pants"), + ], + default="General", + null=True, + ), + ), + ("start", models.IntegerField(default=0)), + ("end", models.IntegerField(default=0)), + ( + "eatery", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="events", + to="eatery.eatery", + ), + ), ], ), ] diff --git a/src/event/migrations/0002_alter_event_id.py b/src/event/migrations/0002_alter_event_id.py index 6570118..77c5cbe 100644 --- a/src/event/migrations/0002_alter_event_id.py +++ b/src/event/migrations/0002_alter_event_id.py @@ -6,13 +6,15 @@ class Migration(migrations.Migration): dependencies = [ - ('event', '0001_initial'), + ("event", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='event', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + model_name="event", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), ), ] diff --git a/src/event/migrations/0003_alter_event_event_description.py b/src/event/migrations/0003_alter_event_event_description.py index 86afd1b..ef70791 100644 --- a/src/event/migrations/0003_alter_event_event_description.py +++ b/src/event/migrations/0003_alter_event_event_description.py @@ -6,13 +6,26 @@ class Migration(migrations.Migration): dependencies = [ - ('event', '0002_alter_event_id'), + ("event", "0002_alter_event_id"), ] operations = [ migrations.AlterField( - model_name='event', - name='event_description', - field=models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Open', 'Cafe'), ('Pants', 'Pants')], default='General', null=True), + model_name="event", + name="event_description", + field=models.TextField( + blank=True, + choices=[ + ("Breakfast", "Breakfast"), + ("Brunch", "Brunch"), + ("Lunch", "Lunch"), + ("Dinner", "Dinner"), + ("General", "General"), + ("Open", "Cafe"), + ("Pants", "Pants"), + ], + default="General", + null=True, + ), ), ] diff --git a/src/event/migrations/0004_alter_event_event_description.py b/src/event/migrations/0004_alter_event_event_description.py index b3f7d98..4a8ca04 100644 --- a/src/event/migrations/0004_alter_event_event_description.py +++ b/src/event/migrations/0004_alter_event_event_description.py @@ -6,13 +6,26 @@ class Migration(migrations.Migration): dependencies = [ - ('event', '0003_alter_event_event_description'), + ("event", "0003_alter_event_event_description"), ] operations = [ migrations.AlterField( - model_name='event', - name='event_description', - field=models.TextField(blank=True, choices=[('Breakfast', 'Breakfast'), ('Brunch', 'Brunch'), ('Lunch', 'Lunch'), ('Dinner', 'Dinner'), ('General', 'General'), ('Cafe', 'Cafe'), ('Pants', 'Pants')], default='General', null=True), + model_name="event", + name="event_description", + field=models.TextField( + blank=True, + choices=[ + ("Breakfast", "Breakfast"), + ("Brunch", "Brunch"), + ("Lunch", "Lunch"), + ("Dinner", "Dinner"), + ("General", "General"), + ("Cafe", "Cafe"), + ("Pants", "Pants"), + ], + default="General", + null=True, + ), ), ] diff --git a/src/event/models.py b/src/event/models.py index d71a303..e0a4d86 100644 --- a/src/event/models.py +++ b/src/event/models.py @@ -1,8 +1,9 @@ -from django.db import models +from django.db import models from django.db import connection from eatery.models import Eatery + class EventDescription(models.TextChoices): BREAKFAST = "Breakfast" BRUNCH = "Brunch" @@ -12,17 +13,24 @@ class EventDescription(models.TextChoices): CAFE = "Cafe" PANTS = "Pants" -class Event(models.Model): - eatery = models.ForeignKey(Eatery, related_name = "events", on_delete=models.DO_NOTHING) + +class Event(models.Model): + eatery = models.ForeignKey( + Eatery, related_name="events", on_delete=models.DO_NOTHING + ) event_description = models.TextField( - choices=EventDescription.choices, default = EventDescription.GENERAL, blank=True, null = True) - start = models.IntegerField(default = 0) - end = models.IntegerField(default = 0) - + choices=EventDescription.choices, + default=EventDescription.GENERAL, + blank=True, + null=True, + ) + start = models.IntegerField(default=0) + end = models.IntegerField(default=0) + def __str__(self): return f"{self.eatery.name}: {self.event_description} from {self.start} to {self.end}" @classmethod def truncate(cls): with connection.cursor() as cursor: - cursor.execute('TRUNCATE TABLE {} CASCADE'.format(cls._meta.db_table)) \ No newline at end of file + cursor.execute("TRUNCATE TABLE {} CASCADE".format(cls._meta.db_table)) diff --git a/src/event/serializers.py b/src/event/serializers.py index 435a044..65b0259 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -3,9 +3,12 @@ from category.serializers import CategorySerializer, CategorySerializerOptimized from datetime import datetime + class EventSerializer(serializers.ModelSerializer): id = serializers.IntegerField(required=False, read_only=True) - event_description = serializers.CharField(allow_null=True, allow_blank=True, default=None) + event_description = serializers.CharField( + allow_null=True, allow_blank=True, default=None + ) start = serializers.IntegerField() end = serializers.IntegerField() menu = CategorySerializer(many=True, read_only=True) @@ -18,6 +21,7 @@ class Meta: model = Event fields = ["id", "eatery", "event_description", "start", "end", "menu"] + class EventSerializerOptimized(serializers.ModelSerializer): menu = CategorySerializerOptimized(many=True, read_only=True) @@ -25,6 +29,7 @@ class Meta: model = Event fields = ["id", "event_description", "start", "end", "menu"] + class EventSerializerSimple(serializers.ModelSerializer): class Meta: model = Event diff --git a/src/event/urls.py b/src/event/urls.py index 5c0709c..072081d 100644 --- a/src/event/urls.py +++ b/src/event/urls.py @@ -7,4 +7,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/event/views.py b/src/event/views.py index a0bdf91..85b3022 100644 --- a/src/event/views.py +++ b/src/event/views.py @@ -2,6 +2,7 @@ from event.models import Event from event.serializers import EventSerializer + class EventViewSet(viewsets.ModelViewSet): queryset = Event.objects.all() - serializer_class = EventSerializer \ No newline at end of file + serializer_class = EventSerializer diff --git a/src/item/apps.py b/src/item/apps.py index f290658..77040d0 100644 --- a/src/item/apps.py +++ b/src/item/apps.py @@ -2,5 +2,5 @@ class ItemConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'item' + default_auto_field = "django.db.models.BigAutoField" + name = "item" diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 132d775..6156eea 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -6,50 +6,46 @@ import json from util.constants import eatery_is_cafe -class PopulateItemController(): + +class PopulateItemController: def __init__(self): - self = self + self = self def generate_cafe_items(self, menu, json_eatery): - for json_item in json_eatery["diningItems"]: - category_name = json_item['category'].strip() + for json_item in json_eatery["diningItems"]: + category_name = json_item["category"].strip() try: category_id = menu[category_name] except KeyError: continue - data = { - "category" : category_id, - "name" : json_item["item"] - } + data = {"category": category_id, "name": json_item["item"]} item = ItemSerializer(data=data) if item.is_valid(): item.save() else: print(item.errors) - def generate_dining_hall_items(self, menu, json_event, json_eatery): - json_menus = json_event['menu'] + json_menus = json_event["menu"] for json_menu in json_menus: - category_name = json_menu['category'].strip() + category_name = json_menu["category"].strip() category_id = menu[category_name] - for json_item in json_menu['items']: - data = { - "category" : category_id, - "name" : json_item["item"] - } + for json_item in json_menu["items"]: + data = {"category": category_id, "name": json_item["item"]} item = ItemSerializer(data=data) if item.is_valid(): item.save() - else: - print(item.errors) + else: + print(item.errors) def process(self, categories_dict, json_eateries): - with open("./static_sources/external_eateries.json", "r") as external_eateries_file: + with open( + "./static_sources/external_eateries.json", "r" + ) as external_eateries_file: external_eateries_json = json.load(external_eateries_file) json_eateries.extend(external_eateries_json["eateries"]) @@ -65,14 +61,15 @@ def process(self, categories_dict, json_eateries): is_cafe = eatery_is_cafe(json_eatery) json_dates = json_eatery["operatingHours"] - for json_date in json_dates: + for json_date in json_dates: json_events = json_date["events"] for json_event in json_events: if i < len(iter): menu_id = iter[i] - menu = eatery_menus[menu_id]; i += 1 + menu = eatery_menus[menu_id] + i += 1 - if is_cafe: + if is_cafe: self.generate_cafe_items(menu, json_eatery) - else: + else: self.generate_dining_hall_items(menu, json_event, json_eatery) diff --git a/src/item/migrations/0001_initial.py b/src/item/migrations/0001_initial.py index 621174f..d9a19c6 100644 --- a/src/item/migrations/0001_initial.py +++ b/src/item/migrations/0001_initial.py @@ -9,17 +9,24 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('category', '0001_initial'), + ("category", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Item', + name="Item", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(default='Item', max_length=40)), - ('base_price', models.FloatField(blank=True, default=0.0, null=True)), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='items', to='category.category')), + ("id", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(default="Item", max_length=40)), + ("base_price", models.FloatField(blank=True, default=0.0, null=True)), + ( + "category", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="items", + to="category.category", + ), + ), ], ), ] diff --git a/src/item/migrations/0002_alter_item_id.py b/src/item/migrations/0002_alter_item_id.py index 7d59a20..8afff18 100644 --- a/src/item/migrations/0002_alter_item_id.py +++ b/src/item/migrations/0002_alter_item_id.py @@ -6,13 +6,15 @@ class Migration(migrations.Migration): dependencies = [ - ('item', '0001_initial'), + ("item", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='item', - name='id', - field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + model_name="item", + name="id", + field=models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), ), ] diff --git a/src/item/models.py b/src/item/models.py index 223539d..0ea8194 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -2,10 +2,13 @@ from eatery.models import Eatery from category.models import Category + class Item(models.Model): - category = models.ForeignKey(Category, related_name = "items", on_delete=models.DO_NOTHING) - name = models.CharField(max_length=40, default = "Item") + category = models.ForeignKey( + Category, related_name="items", on_delete=models.DO_NOTHING + ) + name = models.CharField(max_length=40, default="Item") base_price = models.FloatField(null=True, blank=True, default=0.0) - + def __str__(self): return f"{self.name} ({self.category.name})" diff --git a/src/item/serializers.py b/src/item/serializers.py index 519109e..620f86a 100644 --- a/src/item/serializers.py +++ b/src/item/serializers.py @@ -1,19 +1,21 @@ from rest_framework import serializers from item.models import Item + class ItemSerializer(serializers.ModelSerializer): - id = serializers.IntegerField(read_only = True) - name = serializers.CharField(default = "Item") + id = serializers.IntegerField(read_only=True) + name = serializers.CharField(default="Item") def create(self, validated_data): - item, _ = Item.objects.get_or_create(**validated_data) + item, _ = Item.objects.get_or_create(**validated_data) return item class Meta: model = Item - fields = ['id', 'category', 'name'] + fields = ["id", "category", "name"] + class ItemSerializerOptimized(serializers.ModelSerializer): class Meta: model = Item - fields = ['id', 'name'] \ No newline at end of file + fields = ["id", "name"] diff --git a/src/item/urls.py b/src/item/urls.py index 6c5de78..3827d0a 100644 --- a/src/item/urls.py +++ b/src/item/urls.py @@ -7,4 +7,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/item/views.py b/src/item/views.py index 745b028..9ca7171 100644 --- a/src/item/views.py +++ b/src/item/views.py @@ -2,6 +2,7 @@ from item.models import Item from item.serializers import ItemSerializer + class ItemViewSet(viewsets.ModelViewSet): queryset = Item.objects.all() - serializer_class = ItemSerializer \ No newline at end of file + serializer_class = ItemSerializer diff --git a/src/manage.py b/src/manage.py index ad32ebf..b469b27 100755 --- a/src/manage.py +++ b/src/manage.py @@ -6,8 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", - "eatery_blue_backend.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eatery_blue_backend.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/src/person/admin.py b/src/person/admin.py index 8b935f3..04a8de9 100644 --- a/src/person/admin.py +++ b/src/person/admin.py @@ -2,4 +2,4 @@ from person.models import Student, Chef admin.site.register(Student) -admin.site.register(Chef) \ No newline at end of file +admin.site.register(Chef) diff --git a/src/person/apps.py b/src/person/apps.py index b30fee4..8f12bd7 100644 --- a/src/person/apps.py +++ b/src/person/apps.py @@ -2,5 +2,5 @@ class PersonConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'person' + default_auto_field = "django.db.models.BigAutoField" + name = "person" diff --git a/src/person/migrations/0001_initial.py b/src/person/migrations/0001_initial.py index 0bbad96..f5a02be 100644 --- a/src/person/migrations/0001_initial.py +++ b/src/person/migrations/0001_initial.py @@ -9,28 +9,67 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('item', '0001_initial'), - ('eatery', '0001_initial'), - ('auth', '0012_alter_user_first_name_max_length'), + ("item", "0001_initial"), + ("eatery", "0001_initial"), + ("auth", "0012_alter_user_first_name_max_length"), ] operations = [ migrations.CreateModel( - name='Student', + name="Student", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('net_id', models.TextField()), - ('favorite_eateries', models.ManyToManyField(related_name='student', to='eatery.Eatery')), - ('favorite_items', models.ManyToManyField(related_name='student', to='item.Item')), - ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("net_id", models.TextField()), + ( + "favorite_eateries", + models.ManyToManyField(related_name="student", to="eatery.Eatery"), + ), + ( + "favorite_items", + models.ManyToManyField(related_name="student", to="item.Item"), + ), + ( + "user", + models.OneToOneField( + default=None, + on_delete=django.db.models.deletion.CASCADE, + to="auth.user", + ), + ), ], ), migrations.CreateModel( - name='Chef', + name="Chef", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('eateries_managed', models.ManyToManyField(related_name='chef', to='eatery.Eatery')), - ('user', models.OneToOneField(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.user')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "eateries_managed", + models.ManyToManyField(related_name="chef", to="eatery.Eatery"), + ), + ( + "user", + models.OneToOneField( + default=None, + on_delete=django.db.models.deletion.CASCADE, + to="auth.user", + ), + ), ], ), ] diff --git a/src/person/models.py b/src/person/models.py index 82067aa..69cad5d 100644 --- a/src/person/models.py +++ b/src/person/models.py @@ -4,16 +4,21 @@ class Student(models.Model): net_id = models.TextField() - favorite_eateries = models.ManyToManyField('eatery.Eatery', related_name='student') - favorite_items = models.ManyToManyField('item.Item', related_name='student') - user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) - + favorite_eateries = models.ManyToManyField("eatery.Eatery", related_name="student") + favorite_items = models.ManyToManyField("item.Item", related_name="student") + user = models.OneToOneField( + User, on_delete=models.CASCADE, unique=True, default=None + ) + def __str__(self): return f"Student {self.net_id}" + class Chef(models.Model): - eateries_managed = models.ManyToManyField('eatery.Eatery', related_name='chef') - user = models.OneToOneField(User, on_delete=models.CASCADE, unique=True, default=None) - + eateries_managed = models.ManyToManyField("eatery.Eatery", related_name="chef") + user = models.OneToOneField( + User, on_delete=models.CASCADE, unique=True, default=None + ) + def __str__(self): - return f"Chef {self.user.username}" \ No newline at end of file + return f"Chef {self.user.username}" diff --git a/src/person/serializers.py b/src/person/serializers.py index a5e3942..00061aa 100644 --- a/src/person/serializers.py +++ b/src/person/serializers.py @@ -3,12 +3,14 @@ from person.models import Student, Chef from django.contrib.auth.models import User + class StudentSerializer(serializers.ModelSerializer): class Meta: model = Student - fields = ['id', 'net_id', 'user', 'favorite_eateries', 'favorite_items'] + fields = ["id", "net_id", "user", "favorite_eateries", "favorite_items"] + class ChefSerializer(serializers.ModelSerializer): class Meta: model = Chef - fields = ['id', 'user', 'eateries_managed'] + fields = ["id", "user", "eateries_managed"] diff --git a/src/person/urls.py b/src/person/urls.py index 75e30dc..e219b74 100644 --- a/src/person/urls.py +++ b/src/person/urls.py @@ -8,4 +8,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/person/views.py b/src/person/views.py index 778d6e7..ec981a4 100644 --- a/src/person/views.py +++ b/src/person/views.py @@ -2,10 +2,12 @@ from person.models import Student, Chef from person.serializers import StudentSerializer, ChefSerializer + class StudentViewSet(viewsets.ModelViewSet): queryset = Student.objects.all() serializer_class = StudentSerializer + class ChefViewSet(viewsets.ModelViewSet): queryset = Chef.objects.all() - serializer_class = ChefSerializer \ No newline at end of file + serializer_class = ChefSerializer diff --git a/src/report/apps.py b/src/report/apps.py index a3e9973..f02dd49 100644 --- a/src/report/apps.py +++ b/src/report/apps.py @@ -2,5 +2,5 @@ class ReportConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = "report" \ No newline at end of file + default_auto_field = "django.db.models.BigAutoField" + name = "report" diff --git a/src/report/migrations/0001_initial.py b/src/report/migrations/0001_initial.py index 7fa1323..fbfe01a 100644 --- a/src/report/migrations/0001_initial.py +++ b/src/report/migrations/0001_initial.py @@ -9,17 +9,33 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('eatery', '0001_initial'), + ("eatery", "0001_initial"), ] operations = [ migrations.CreateModel( - name='Report', + name="Report", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('content', models.TextField()), - ('created', models.DateTimeField(auto_now_add=True)), - ('eatery', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='eatery.eatery')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("content", models.TextField()), + ("created", models.DateTimeField(auto_now_add=True)), + ( + "eatery", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="eatery.eatery", + ), + ), ], ), ] diff --git a/src/report/migrations/0002_report_netid.py b/src/report/migrations/0002_report_netid.py index 22c3b63..1bfc648 100644 --- a/src/report/migrations/0002_report_netid.py +++ b/src/report/migrations/0002_report_netid.py @@ -6,13 +6,13 @@ class Migration(migrations.Migration): dependencies = [ - ('report', '0001_initial'), + ("report", "0001_initial"), ] operations = [ migrations.AddField( - model_name='report', - name='netid', + model_name="report", + name="netid", field=models.CharField(blank=True, max_length=10, null=True), ), ] diff --git a/src/report/models.py b/src/report/models.py index 6b81978..f331648 100644 --- a/src/report/models.py +++ b/src/report/models.py @@ -7,8 +7,6 @@ class Report(models.Model): netid = models.CharField(max_length=10, null=True, blank=True) content = models.TextField() created = models.DateTimeField(auto_now_add=True) - + def __str__(self): - return f'{self.content} - {self.created}' - - + return f"{self.content} - {self.created}" diff --git a/src/report/serializers.py b/src/report/serializers.py index 1356886..6e7c4a9 100644 --- a/src/report/serializers.py +++ b/src/report/serializers.py @@ -5,4 +5,4 @@ class ReportSerializer(serializers.ModelSerializer): class Meta: model = Report - fields = ['id', 'eatery', 'netid', 'content', 'created'] + fields = ["id", "eatery", "netid", "content", "created"] diff --git a/src/report/urls.py b/src/report/urls.py index e4a4df6..2977993 100644 --- a/src/report/urls.py +++ b/src/report/urls.py @@ -8,4 +8,4 @@ urlpatterns = [ path("", include(router.urls)), -] \ No newline at end of file +] diff --git a/src/util/constants.py b/src/util/constants.py index c658cf0..47c3f86 100644 --- a/src/util/constants.py +++ b/src/util/constants.py @@ -1,2 +1,4 @@ def eatery_is_cafe(json_eatery): - return not "Dining Room" in [eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"]] \ No newline at end of file + return not "Dining Room" in [ + eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] + ] From 70725b0f8bbf2a2ac5a27616f583e85cdb9a27cf Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 5 Sep 2024 12:15:54 -0400 Subject: [PATCH 247/305] Adding linter, running it on codebase, and fixing initial errors --- .pre-commit-config.yaml | 14 +++++++------- src/category/controllers/populate_category.py | 2 -- src/eatery/controllers/update_eatery.py | 2 +- src/eatery/datatype/Eatery.py | 3 --- src/eatery/models.py | 1 - src/eatery/util/json.py | 2 +- src/eatery/views.py | 2 +- src/event/datatype/Event.py | 2 +- src/event/serializers.py | 1 - src/item/controllers/populate_item.py | 4 ---- src/item/models.py | 1 - src/person/serializers.py | 1 - src/util/constants.py | 2 +- test.py | 3 --- 14 files changed, 12 insertions(+), 28 deletions(-) delete mode 100644 test.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e08eb5..44a7d08 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,10 +9,10 @@ repos: # pre-commit's default_language_version, see # https://pre-commit.com/#top_level-default_language_version language_version: python3.9 - # - repo: https://github.com/astral-sh/ruff-pre-commit - # # Ruff version. - # rev: v0.6.3 - # hooks: - # # Run the linter. - # - id: ruff - # args: [--fix] + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.6.3 + hooks: + # Run the linter. + - id: ruff + args: [--fix] diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index e8a23fd..7d627ac 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -1,6 +1,4 @@ -from category.models import Category from category.serializers import CategorySerializer -from item.models import Item import json from util.constants import eatery_is_cafe diff --git a/src/eatery/controllers/update_eatery.py b/src/eatery/controllers/update_eatery.py index 7bb3752..e6dafd2 100644 --- a/src/eatery/controllers/update_eatery.py +++ b/src/eatery/controllers/update_eatery.py @@ -64,7 +64,7 @@ def upload_image(self, image): try: return response.json()["data"] - except: + except Exception: raise Exception("Image uploading unsuccessful") """ diff --git a/src/eatery/datatype/Eatery.py b/src/eatery/datatype/Eatery.py index 20f1413..bcbe5f3 100644 --- a/src/eatery/datatype/Eatery.py +++ b/src/eatery/datatype/Eatery.py @@ -1,8 +1,5 @@ -from datetime import date from enum import Enum -from typing import Optional -import pytz # from event.datatype.Event import Event, filter_range # from api.datatype.WaitTimesDay import WaitTimesDay diff --git a/src/eatery/models.py b/src/eatery/models.py index 6f4ea1e..a7fe69c 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.db import connection from eatery.util.constants import DEFAULT_IMAGE_URL diff --git a/src/eatery/util/json.py b/src/eatery/util/json.py index a7d80af..17688a9 100644 --- a/src/eatery/util/json.py +++ b/src/eatery/util/json.py @@ -23,7 +23,7 @@ def verify_json_fields( if not isinstance(json[field], str): return False elif field_type_map[field] is FieldType.EATERYID: - if not isinstance(json[field], int) or EateryID(json[field]) == None: + if not isinstance(json[field], int) or EateryID(json[field]) is None: return False for field in json: diff --git a/src/eatery/views.py b/src/eatery/views.py index 85fd639..cb5eb8d 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -82,7 +82,7 @@ def update(self, request, *args, **kwargs): id = int(text_params.get("id")) try: image_param = request.FILES.get("image") - except: + except Exception: image_param = None try: diff --git a/src/event/datatype/Event.py b/src/event/datatype/Event.py index 79bb19d..a6d2912 100644 --- a/src/event/datatype/Event.py +++ b/src/event/datatype/Event.py @@ -4,7 +4,7 @@ import pytz from datatype.Menu import Menu -# from api.util.time import combined_timestamp +from api.util.time import combined_timestamp class Event: diff --git a/src/event/serializers.py b/src/event/serializers.py index 65b0259..daa7202 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -1,7 +1,6 @@ from rest_framework import serializers from event.models import Event from category.serializers import CategorySerializer, CategorySerializerOptimized -from datetime import datetime class EventSerializer(serializers.ModelSerializer): diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 6156eea..643bdbc 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -1,8 +1,4 @@ -from item.models import Item from item.serializers import ItemSerializer -from eatery.models import Eatery -from eatery.serializers import EaterySerializer -import string import json from util.constants import eatery_is_cafe diff --git a/src/item/models.py b/src/item/models.py index 0ea8194..885d9ad 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -1,5 +1,4 @@ from django.db import models -from eatery.models import Eatery from category.models import Category diff --git a/src/person/serializers.py b/src/person/serializers.py index 00061aa..b304fcb 100644 --- a/src/person/serializers.py +++ b/src/person/serializers.py @@ -1,7 +1,6 @@ from rest_framework import serializers from person.models import Student, Chef -from django.contrib.auth.models import User class StudentSerializer(serializers.ModelSerializer): diff --git a/src/util/constants.py b/src/util/constants.py index 47c3f86..80c7cb9 100644 --- a/src/util/constants.py +++ b/src/util/constants.py @@ -1,4 +1,4 @@ def eatery_is_cafe(json_eatery): - return not "Dining Room" in [ + return "Dining Room" not in [ eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] ] diff --git a/test.py b/test.py deleted file mode 100644 index 3e905cf..0000000 --- a/test.py +++ /dev/null @@ -1,3 +0,0 @@ -import os - -a = [1, 2, 3] From 2575238afd69bbeb0d7ad94341d34fe218861111 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Thu, 5 Sep 2024 12:15:54 -0400 Subject: [PATCH 248/305] Adding linter, running it on codebase, and fixing initial errors --- .pre-commit-config.yaml | 14 +++++++------- src/category/controllers/populate_category.py | 2 -- src/eatery/controllers/update_eatery.py | 2 +- src/eatery/datatype/Eatery.py | 3 --- src/eatery/models.py | 1 - src/eatery/util/json.py | 2 +- src/eatery/views.py | 2 +- src/event/datatype/Event.py | 2 +- src/event/serializers.py | 1 - src/item/controllers/populate_item.py | 4 ---- src/item/models.py | 1 - src/person/serializers.py | 1 - src/util/constants.py | 2 +- test.py | 3 --- 14 files changed, 12 insertions(+), 28 deletions(-) delete mode 100644 test.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e08eb5..44a7d08 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,10 +9,10 @@ repos: # pre-commit's default_language_version, see # https://pre-commit.com/#top_level-default_language_version language_version: python3.9 - # - repo: https://github.com/astral-sh/ruff-pre-commit - # # Ruff version. - # rev: v0.6.3 - # hooks: - # # Run the linter. - # - id: ruff - # args: [--fix] + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.6.3 + hooks: + # Run the linter. + - id: ruff + args: [--fix] diff --git a/src/category/controllers/populate_category.py b/src/category/controllers/populate_category.py index e8a23fd..7d627ac 100644 --- a/src/category/controllers/populate_category.py +++ b/src/category/controllers/populate_category.py @@ -1,6 +1,4 @@ -from category.models import Category from category.serializers import CategorySerializer -from item.models import Item import json from util.constants import eatery_is_cafe diff --git a/src/eatery/controllers/update_eatery.py b/src/eatery/controllers/update_eatery.py index 7bb3752..e6dafd2 100644 --- a/src/eatery/controllers/update_eatery.py +++ b/src/eatery/controllers/update_eatery.py @@ -64,7 +64,7 @@ def upload_image(self, image): try: return response.json()["data"] - except: + except Exception: raise Exception("Image uploading unsuccessful") """ diff --git a/src/eatery/datatype/Eatery.py b/src/eatery/datatype/Eatery.py index 20f1413..bcbe5f3 100644 --- a/src/eatery/datatype/Eatery.py +++ b/src/eatery/datatype/Eatery.py @@ -1,8 +1,5 @@ -from datetime import date from enum import Enum -from typing import Optional -import pytz # from event.datatype.Event import Event, filter_range # from api.datatype.WaitTimesDay import WaitTimesDay diff --git a/src/eatery/models.py b/src/eatery/models.py index 6f4ea1e..a7fe69c 100644 --- a/src/eatery/models.py +++ b/src/eatery/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.db import connection from eatery.util.constants import DEFAULT_IMAGE_URL diff --git a/src/eatery/util/json.py b/src/eatery/util/json.py index a7d80af..17688a9 100644 --- a/src/eatery/util/json.py +++ b/src/eatery/util/json.py @@ -23,7 +23,7 @@ def verify_json_fields( if not isinstance(json[field], str): return False elif field_type_map[field] is FieldType.EATERYID: - if not isinstance(json[field], int) or EateryID(json[field]) == None: + if not isinstance(json[field], int) or EateryID(json[field]) is None: return False for field in json: diff --git a/src/eatery/views.py b/src/eatery/views.py index 85fd639..cb5eb8d 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -82,7 +82,7 @@ def update(self, request, *args, **kwargs): id = int(text_params.get("id")) try: image_param = request.FILES.get("image") - except: + except Exception: image_param = None try: diff --git a/src/event/datatype/Event.py b/src/event/datatype/Event.py index 79bb19d..a6d2912 100644 --- a/src/event/datatype/Event.py +++ b/src/event/datatype/Event.py @@ -4,7 +4,7 @@ import pytz from datatype.Menu import Menu -# from api.util.time import combined_timestamp +from api.util.time import combined_timestamp class Event: diff --git a/src/event/serializers.py b/src/event/serializers.py index 65b0259..daa7202 100644 --- a/src/event/serializers.py +++ b/src/event/serializers.py @@ -1,7 +1,6 @@ from rest_framework import serializers from event.models import Event from category.serializers import CategorySerializer, CategorySerializerOptimized -from datetime import datetime class EventSerializer(serializers.ModelSerializer): diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 6156eea..643bdbc 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -1,8 +1,4 @@ -from item.models import Item from item.serializers import ItemSerializer -from eatery.models import Eatery -from eatery.serializers import EaterySerializer -import string import json from util.constants import eatery_is_cafe diff --git a/src/item/models.py b/src/item/models.py index 0ea8194..885d9ad 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -1,5 +1,4 @@ from django.db import models -from eatery.models import Eatery from category.models import Category diff --git a/src/person/serializers.py b/src/person/serializers.py index 00061aa..b304fcb 100644 --- a/src/person/serializers.py +++ b/src/person/serializers.py @@ -1,7 +1,6 @@ from rest_framework import serializers from person.models import Student, Chef -from django.contrib.auth.models import User class StudentSerializer(serializers.ModelSerializer): diff --git a/src/util/constants.py b/src/util/constants.py index 47c3f86..80c7cb9 100644 --- a/src/util/constants.py +++ b/src/util/constants.py @@ -1,4 +1,4 @@ def eatery_is_cafe(json_eatery): - return not "Dining Room" in [ + return "Dining Room" not in [ eatery_type["descr"] for eatery_type in json_eatery["eateryTypes"] ] diff --git a/test.py b/test.py deleted file mode 100644 index 3e905cf..0000000 --- a/test.py +++ /dev/null @@ -1,3 +0,0 @@ -import os - -a = [1, 2, 3] From 8d403f2450e5e5eaa63a86da4aae9173e6cff257 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Mon, 23 Sep 2024 18:30:01 -0400 Subject: [PATCH 249/305] Adding swagger and bandit --- .gitignore | 1 + .pre-commit-config.yaml | 4 ++++ README.md | 4 ++-- requirements.txt | 10 ++++++++++ src/eatery_blue_backend/settings.py | 1 + src/eatery_blue_backend/urls.py | 13 +++++++++++++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 749e7f7..e86bb74 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ static_sources/data.log __pycache__/ *.py[cod] *$py.class +.ruff_cache # More Python stuff .DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 44a7d08..93a41ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,3 +16,7 @@ repos: # Run the linter. - id: ruff args: [--fix] + - repo: https://github.com/PyCQA/bandit + rev: 1.7.0 + hooks: + - id: bandit diff --git a/README.md b/README.md index 0f65339..66e73fb 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # eatery-blue-backend -This is the backend for eatery-blue-backend. +This is the backend for Eatery Blue. # Postgres Setup --- -- Install PostgresSQL here at https://www.postgresql.org/download/ +- Install PostgreSQL here at https://www.postgresql.org/download/ - Login to postgres via command line by entering `psql postgres` - Create the eatery database via `create database "eatery-dev";` - Quit psql via `\q` diff --git a/requirements.txt b/requirements.txt index f0e1f1f..f7c31ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,12 @@ certifi==2021.10.8 cfgv==3.4.0 charset-normalizer==2.0.9 click==8.0.3 +coreapi==2.3.3 +coreschema==0.0.4 distlib==0.3.8 Django==4.0 djangorestframework==3.13.1 +drf-yasg==1.21.7 filelock==3.15.4 google-api-core==2.3.2 google-api-python-client==2.33.0 @@ -17,9 +20,15 @@ googleapis-common-protos==1.54.0 httplib2==0.20.2 identify==2.6.0 idna==3.3 +inflection==0.5.1 +itypes==1.2.0 +Jinja2==3.1.4 +MarkupSafe==2.1.5 mypy-extensions==0.4.3 nodeenv==1.9.1 oauthlib==3.1.1 +openapi-codec==1.3.2 +packaging==24.1 pathspec==0.9.0 platformdirs==4.2.2 pre-commit==3.8.0 @@ -33,6 +42,7 @@ PyYAML==6.0.2 requests==2.26.0 requests-oauthlib==1.3.0 rsa==4.8 +simplejson==3.19.3 six==1.16.0 sqlparse==0.4.2 tomli==1.2.3 diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 3101d88..22296dd 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -40,6 +40,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework.authtoken", + "drf_yasg", # Apps "eatery_blue_backend", "eatery", diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index f289980..d04ad7b 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -1,5 +1,17 @@ +from drf_yasg import openapi from django.contrib import admin from django.urls import include, path +from drf_yasg.views import get_schema_view +from rest_framework import permissions + +schema_view = get_schema_view( + openapi.Info( + title="Eatery Blue Backend API Docs", + default_version="v1", + ), + public=True, + permission_classes=(permissions.AllowAny,), +) urlpatterns = [ path("admin/", admin.site.urls), @@ -9,4 +21,5 @@ path("category/", include("category.urls")), path("report/", include("report.urls")), path("person/", include("person.urls")), + path("docs/", schema_view.with_ui("swagger", cache_timeout=0)), ] From b81ed2532cb5dd1e004c8ab4ebbf8fa42750b7cd Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Mon, 23 Sep 2024 18:30:01 -0400 Subject: [PATCH 250/305] Adding swagger and bandit --- .gitignore | 1 + .pre-commit-config.yaml | 4 ++++ README.md | 4 ++-- requirements.txt | 10 ++++++++++ src/eatery_blue_backend/settings.py | 1 + src/eatery_blue_backend/urls.py | 13 +++++++++++++ 6 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 749e7f7..e86bb74 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ static_sources/data.log __pycache__/ *.py[cod] *$py.class +.ruff_cache # More Python stuff .DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 44a7d08..93a41ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,3 +16,7 @@ repos: # Run the linter. - id: ruff args: [--fix] + - repo: https://github.com/PyCQA/bandit + rev: 1.7.0 + hooks: + - id: bandit diff --git a/README.md b/README.md index 0f65339..66e73fb 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # eatery-blue-backend -This is the backend for eatery-blue-backend. +This is the backend for Eatery Blue. # Postgres Setup --- -- Install PostgresSQL here at https://www.postgresql.org/download/ +- Install PostgreSQL here at https://www.postgresql.org/download/ - Login to postgres via command line by entering `psql postgres` - Create the eatery database via `create database "eatery-dev";` - Quit psql via `\q` diff --git a/requirements.txt b/requirements.txt index f0e1f1f..f7c31ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,12 @@ certifi==2021.10.8 cfgv==3.4.0 charset-normalizer==2.0.9 click==8.0.3 +coreapi==2.3.3 +coreschema==0.0.4 distlib==0.3.8 Django==4.0 djangorestframework==3.13.1 +drf-yasg==1.21.7 filelock==3.15.4 google-api-core==2.3.2 google-api-python-client==2.33.0 @@ -17,9 +20,15 @@ googleapis-common-protos==1.54.0 httplib2==0.20.2 identify==2.6.0 idna==3.3 +inflection==0.5.1 +itypes==1.2.0 +Jinja2==3.1.4 +MarkupSafe==2.1.5 mypy-extensions==0.4.3 nodeenv==1.9.1 oauthlib==3.1.1 +openapi-codec==1.3.2 +packaging==24.1 pathspec==0.9.0 platformdirs==4.2.2 pre-commit==3.8.0 @@ -33,6 +42,7 @@ PyYAML==6.0.2 requests==2.26.0 requests-oauthlib==1.3.0 rsa==4.8 +simplejson==3.19.3 six==1.16.0 sqlparse==0.4.2 tomli==1.2.3 diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 3101d88..22296dd 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -40,6 +40,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework.authtoken", + "drf_yasg", # Apps "eatery_blue_backend", "eatery", diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index f289980..d04ad7b 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -1,5 +1,17 @@ +from drf_yasg import openapi from django.contrib import admin from django.urls import include, path +from drf_yasg.views import get_schema_view +from rest_framework import permissions + +schema_view = get_schema_view( + openapi.Info( + title="Eatery Blue Backend API Docs", + default_version="v1", + ), + public=True, + permission_classes=(permissions.AllowAny,), +) urlpatterns = [ path("admin/", admin.site.urls), @@ -9,4 +21,5 @@ path("category/", include("category.urls")), path("report/", include("report.urls")), path("person/", include("person.urls")), + path("docs/", schema_view.with_ui("swagger", cache_timeout=0)), ] From 6bc631bfb92ee12f1b4ef10212272d6397eb1fd6 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 24 Sep 2024 10:03:45 -0400 Subject: [PATCH 251/305] Adding pre commit updater --- .pre-commit-config.yaml | 28 ++++++++++++---------------- README.md | 4 ++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93a41ff..0031774 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,22 +1,18 @@ repos: - # Using this mirror lets us use mypyc-compiled black, which is about 2x faster - - repo: https://github.com/psf/black-pre-commit-mirror +- repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update + rev: v0.5.1 + hooks: + - id: pre-commit-update +- repo: https://github.com/psf/black-pre-commit-mirror rev: 24.8.0 hooks: - - id: black - # It is recommended to specify the latest version of Python - # supported by your project here, or alternatively use - # pre-commit's default_language_version, see - # https://pre-commit.com/#top_level-default_language_version - language_version: python3.9 - - repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.6.3 + - id: black +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.7 hooks: - # Run the linter. - - id: ruff + - id: ruff args: [--fix] - - repo: https://github.com/PyCQA/bandit - rev: 1.7.0 +- repo: https://github.com/PyCQA/bandit + rev: 1.7.10 hooks: - - id: bandit + - id: bandit diff --git a/README.md b/README.md index 66e73fb..8523ec4 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ This is the backend for Eatery Blue. - To set up the tables and data (or if reseting the database), make sure current working directory is the `src` folder and run `python3 manage.py makemigrations; python3 manage.py migrate; python3 manage.py populate_models` - To run the backend, run `python3 manage.py runserver 0.0.0.0:8000` (Ensuring the env variables are loaded and all dependencies are installed) +# Documentation + +- Full Swagger Docs API Specs can be found at /docs when running the server + ## SP24 Members - Thomas Vignos From d4153a8a1499045c17f6b82922829c6688869148 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 24 Sep 2024 10:03:45 -0400 Subject: [PATCH 252/305] Adding pre commit updater --- .pre-commit-config.yaml | 28 ++++++++++++---------------- README.md | 4 ++++ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93a41ff..0031774 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,22 +1,18 @@ repos: - # Using this mirror lets us use mypyc-compiled black, which is about 2x faster - - repo: https://github.com/psf/black-pre-commit-mirror +- repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update + rev: v0.5.1 + hooks: + - id: pre-commit-update +- repo: https://github.com/psf/black-pre-commit-mirror rev: 24.8.0 hooks: - - id: black - # It is recommended to specify the latest version of Python - # supported by your project here, or alternatively use - # pre-commit's default_language_version, see - # https://pre-commit.com/#top_level-default_language_version - language_version: python3.9 - - repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.6.3 + - id: black +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.7 hooks: - # Run the linter. - - id: ruff + - id: ruff args: [--fix] - - repo: https://github.com/PyCQA/bandit - rev: 1.7.0 +- repo: https://github.com/PyCQA/bandit + rev: 1.7.10 hooks: - - id: bandit + - id: bandit diff --git a/README.md b/README.md index 66e73fb..8523ec4 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ This is the backend for Eatery Blue. - To set up the tables and data (or if reseting the database), make sure current working directory is the `src` folder and run `python3 manage.py makemigrations; python3 manage.py migrate; python3 manage.py populate_models` - To run the backend, run `python3 manage.py runserver 0.0.0.0:8000` (Ensuring the env variables are loaded and all dependencies are installed) +# Documentation + +- Full Swagger Docs API Specs can be found at /docs when running the server + ## SP24 Members - Thomas Vignos From c07e5d99d944f9be9574ec87519de237ed8c7fbf Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 24 Sep 2024 10:29:56 -0400 Subject: [PATCH 253/305] Using ruff formatter --- .pre-commit-config.yaml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0031774..e2d273e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,17 @@ repos: -- repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update + - repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update rev: v0.5.1 hooks: - - id: pre-commit-update -- repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.8.0 - hooks: - - id: black -- repo: https://github.com/astral-sh/ruff-pre-commit + - id: pre-commit-update + + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.7 hooks: - - id: ruff + - id: ruff args: [--fix] -- repo: https://github.com/PyCQA/bandit + - id: ruff-format + + - repo: https://github.com/PyCQA/bandit rev: 1.7.10 hooks: - - id: bandit + - id: bandit From 99f2388ec57b9d3814cca0d7c2bd878aa60b153f Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 24 Sep 2024 10:29:56 -0400 Subject: [PATCH 254/305] Using ruff formatter --- .pre-commit-config.yaml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0031774..e2d273e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,17 @@ repos: -- repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update + - repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update rev: v0.5.1 hooks: - - id: pre-commit-update -- repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.8.0 - hooks: - - id: black -- repo: https://github.com/astral-sh/ruff-pre-commit + - id: pre-commit-update + + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.7 hooks: - - id: ruff + - id: ruff args: [--fix] -- repo: https://github.com/PyCQA/bandit + - id: ruff-format + + - repo: https://github.com/PyCQA/bandit rev: 1.7.10 hooks: - - id: bandit + - id: bandit From 6115d63407be951aa4d3bc5b444b7fa1bbfd6fa1 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 24 Sep 2024 11:02:18 -0400 Subject: [PATCH 255/305] Adding sentry --- .pre-commit-config.yaml | 10 +++++++++- requirements.txt | 4 +++- src/category/migrations/0001_initial.py | 1 - src/category/migrations/0002_alter_category_id.py | 1 - src/eatery/controllers/update_eatery.py | 1 + src/eatery/datatype/EateryAlert.py | 1 - src/eatery/migrations/0001_initial.py | 1 - src/eatery/migrations/0002_alter_eatery_id.py | 1 - src/eatery/migrations/0003_alter_eatery_image_url.py | 1 - .../migrations/0004_alter_eatery_campus_area.py | 1 - .../migrations/0005_alter_eatery_campus_area.py | 1 - src/eatery/permissions.py | 1 - src/eatery/views.py | 12 ++++++------ .../management/commands/populate_models.py | 2 +- src/eatery_blue_backend/settings.py | 12 ++++++++++++ src/eatery_blue_backend/urls.py | 1 + .../controllers/update_models/CornellDiningEvents.py | 2 +- .../controllers/update_models/CornellDiningNow.py | 2 +- .../update_models/schedule/CornellDiningEvents.py | 2 +- src/event/datatype/Menu.py | 1 - src/event/datatype/MenuCategory.py | 1 - src/event/datatype/MenuItem.py | 1 - src/event/datatype/MenuItemSection.py | 1 - src/event/datatype/MenuSubItem.py | 1 - src/event/migrations/0001_initial.py | 1 - src/event/migrations/0002_alter_event_id.py | 1 - .../migrations/0003_alter_event_event_description.py | 1 - .../migrations/0004_alter_event_event_description.py | 1 - src/item/controllers/populate_item.py | 2 -- src/item/migrations/0001_initial.py | 1 - src/item/migrations/0002_alter_item_id.py | 1 - src/manage.py | 1 + src/person/migrations/0001_initial.py | 1 - src/report/migrations/0001_initial.py | 1 - src/report/migrations/0002_report_netid.py | 1 - 35 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2d273e..af043df 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,24 @@ repos: + # Updates all subsequent hooks - repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update rev: v0.5.1 hooks: - id: pre-commit-update + # Linter hook - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.7 hooks: - id: ruff args: [--fix] - - id: ruff-format + # Formatter hook + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.8.0 + hooks: + - id: black + + # Static code analysis hook - repo: https://github.com/PyCQA/bandit rev: 1.7.10 hooks: diff --git a/requirements.txt b/requirements.txt index f7c31ad..e6b7e10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ coreapi==2.3.3 coreschema==0.0.4 distlib==0.3.8 Django==4.0 +django-rest-swagger==2.2.0 djangorestframework==3.13.1 drf-yasg==1.21.7 filelock==3.15.4 @@ -42,11 +43,12 @@ PyYAML==6.0.2 requests==2.26.0 requests-oauthlib==1.3.0 rsa==4.8 +sentry-sdk==2.14.0 simplejson==3.19.3 six==1.16.0 sqlparse==0.4.2 tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 -urllib3==1.26.7 +urllib3==2.2.3 virtualenv==20.26.3 \ No newline at end of file diff --git a/src/category/migrations/0001_initial.py b/src/category/migrations/0001_initial.py index dca2274..038c293 100644 --- a/src/category/migrations/0001_initial.py +++ b/src/category/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/category/migrations/0002_alter_category_id.py b/src/category/migrations/0002_alter_category_id.py index cbb43d1..c10fb6d 100644 --- a/src/category/migrations/0002_alter_category_id.py +++ b/src/category/migrations/0002_alter_category_id.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("category", "0001_initial"), ] diff --git a/src/eatery/controllers/update_eatery.py b/src/eatery/controllers/update_eatery.py index e6dafd2..04c33c3 100644 --- a/src/eatery/controllers/update_eatery.py +++ b/src/eatery/controllers/update_eatery.py @@ -60,6 +60,7 @@ def upload_image(self, image): "bucket": os.environ["IMAGE_BUCKET"], "image": f"data:image/{extension};base64,{b64_encoded_image}", }, + timeout=10, ) try: diff --git a/src/eatery/datatype/EateryAlert.py b/src/eatery/datatype/EateryAlert.py index ffd307b..7dafc3c 100644 --- a/src/eatery/datatype/EateryAlert.py +++ b/src/eatery/datatype/EateryAlert.py @@ -1,5 +1,4 @@ class EateryAlert: - def __init__( self, id: int, description: str, start_timestamp: int, end_timestamp: int ): diff --git a/src/eatery/migrations/0001_initial.py b/src/eatery/migrations/0001_initial.py index 87246d5..30c531e 100644 --- a/src/eatery/migrations/0001_initial.py +++ b/src/eatery/migrations/0001_initial.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] diff --git a/src/eatery/migrations/0002_alter_eatery_id.py b/src/eatery/migrations/0002_alter_eatery_id.py index 2b70b83..0bcb3d6 100644 --- a/src/eatery/migrations/0002_alter_eatery_id.py +++ b/src/eatery/migrations/0002_alter_eatery_id.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eatery", "0001_initial"), ] diff --git a/src/eatery/migrations/0003_alter_eatery_image_url.py b/src/eatery/migrations/0003_alter_eatery_image_url.py index c276bb6..f975e32 100644 --- a/src/eatery/migrations/0003_alter_eatery_image_url.py +++ b/src/eatery/migrations/0003_alter_eatery_image_url.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eatery", "0002_alter_eatery_id"), ] diff --git a/src/eatery/migrations/0004_alter_eatery_campus_area.py b/src/eatery/migrations/0004_alter_eatery_campus_area.py index 77d43bd..619c925 100644 --- a/src/eatery/migrations/0004_alter_eatery_campus_area.py +++ b/src/eatery/migrations/0004_alter_eatery_campus_area.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eatery", "0003_alter_eatery_image_url"), ] diff --git a/src/eatery/migrations/0005_alter_eatery_campus_area.py b/src/eatery/migrations/0005_alter_eatery_campus_area.py index df3bfb1..1b219b1 100644 --- a/src/eatery/migrations/0005_alter_eatery_campus_area.py +++ b/src/eatery/migrations/0005_alter_eatery_campus_area.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eatery", "0004_alter_eatery_campus_area"), ] diff --git a/src/eatery/permissions.py b/src/eatery/permissions.py index 80a9513..1608cd2 100644 --- a/src/eatery/permissions.py +++ b/src/eatery/permissions.py @@ -2,7 +2,6 @@ class EateryPermission(permissions.BasePermission): - def has_permission(self, request, view): if view.action in ["list", "retrieve"]: return True diff --git a/src/eatery/views.py b/src/eatery/views.py index cb5eb8d..3c6f6db 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -42,12 +42,12 @@ def get_object(self): queryset = self.filter_queryset(self.get_queryset()) lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - assert lookup_url_kwarg in self.kwargs, ( - "Expected view %s to be called with a URL keyword argument " - 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - "attribute on the view correctly." - % (self.__class__.__name__, lookup_url_kwarg) - ) + # assert lookup_url_kwarg in self.kwargs, ( + # "Expected view %s to be called with a URL keyword argument " + # 'named "%s". Fix your URL conf, or set the `.lookup_field` ' + # "attribute on the view correctly." + # % (self.__class__.__name__, lookup_url_kwarg) + # ) # Uses the lookup_field attribute, which defaults to `pk` filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} obj = get_object_or_404(queryset, **filter_kwargs) diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index 952a733..1127546 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -22,7 +22,7 @@ def handle(self, *args, **kwargs): def get_json(self): try: - response = requests.get(CORNELL_DINING_URL) + response = requests.get(CORNELL_DINING_URL, timeout=10) except Exception as e: raise e if response.status_code <= 400: diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 22296dd..d3543d2 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -12,6 +12,18 @@ import os from pathlib import Path +import sentry_sdk + +sentry_sdk.init( + dsn="https://0f2451bfd9bf84c1858cf7319bd51425@o4507365244010496.ingest.us.sentry.io/4508008440856576", + # Set traces_sample_rate to 1.0 to capture 100% + # of transactions for tracing. + traces_sample_rate=1.0, + # Set profiles_sample_rate to 1.0 to profile 100% + # of sampled transactions. + # We recommend adjusting this value in production. + profiles_sample_rate=1.0, +) # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index d04ad7b..2393115 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -13,6 +13,7 @@ permission_classes=(permissions.AllowAny,), ) + urlpatterns = [ path("admin/", admin.site.urls), path("eatery/", include("eatery.urls")), diff --git a/src/event/controllers/update_models/CornellDiningEvents.py b/src/event/controllers/update_models/CornellDiningEvents.py index 40301b3..a3debc5 100644 --- a/src/event/controllers/update_models/CornellDiningEvents.py +++ b/src/event/controllers/update_models/CornellDiningEvents.py @@ -18,7 +18,7 @@ def __init__(self, eatery_id: EateryID, cache): def __call__(self, *args, **kwargs) -> list[Eatery]: if "eateries" not in self.cache: try: - response = requests.get(CORNELL_DINING_URL).json() + response = requests.get(CORNELL_DINING_URL, timeout=10).json() except Exception as e: raise e if response["status"] == "success": diff --git a/src/event/controllers/update_models/CornellDiningNow.py b/src/event/controllers/update_models/CornellDiningNow.py index 98693c7..d668a11 100644 --- a/src/event/controllers/update_models/CornellDiningNow.py +++ b/src/event/controllers/update_models/CornellDiningNow.py @@ -7,7 +7,7 @@ class CornellDiningNow(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: try: - response = requests.get(CORNELL_DINING_URL).json() + response = requests.get(CORNELL_DINING_URL, timeout=10).json() except Exception as e: raise e diff --git a/src/event/controllers/update_models/schedule/CornellDiningEvents.py b/src/event/controllers/update_models/schedule/CornellDiningEvents.py index 40301b3..a3debc5 100644 --- a/src/event/controllers/update_models/schedule/CornellDiningEvents.py +++ b/src/event/controllers/update_models/schedule/CornellDiningEvents.py @@ -18,7 +18,7 @@ def __init__(self, eatery_id: EateryID, cache): def __call__(self, *args, **kwargs) -> list[Eatery]: if "eateries" not in self.cache: try: - response = requests.get(CORNELL_DINING_URL).json() + response = requests.get(CORNELL_DINING_URL, timeout=10).json() except Exception as e: raise e if response["status"] == "success": diff --git a/src/event/datatype/Menu.py b/src/event/datatype/Menu.py index 6e77a77..b7375aa 100644 --- a/src/event/datatype/Menu.py +++ b/src/event/datatype/Menu.py @@ -2,7 +2,6 @@ class Menu: - def __init__(self, categories: list[MenuCategory]): self.categories = categories diff --git a/src/event/datatype/MenuCategory.py b/src/event/datatype/MenuCategory.py index e54262a..55770f9 100644 --- a/src/event/datatype/MenuCategory.py +++ b/src/event/datatype/MenuCategory.py @@ -2,7 +2,6 @@ class MenuCategory: - def __init__(self, category: str, items: list[MenuItem]): self.category = category self.items = items diff --git a/src/event/datatype/MenuItem.py b/src/event/datatype/MenuItem.py index 72b8b33..12a60ee 100644 --- a/src/event/datatype/MenuItem.py +++ b/src/event/datatype/MenuItem.py @@ -4,7 +4,6 @@ class MenuItem: - def __init__( self, name: str, diff --git a/src/event/datatype/MenuItemSection.py b/src/event/datatype/MenuItemSection.py index 1ac44af..fae1105 100644 --- a/src/event/datatype/MenuItemSection.py +++ b/src/event/datatype/MenuItemSection.py @@ -2,7 +2,6 @@ class MenuItemSection: - def __init__(self, name: str, subitems: list[MenuSubItem]): self.name = name self.subitems = subitems diff --git a/src/event/datatype/MenuSubItem.py b/src/event/datatype/MenuSubItem.py index 7c82c28..e236317 100644 --- a/src/event/datatype/MenuSubItem.py +++ b/src/event/datatype/MenuSubItem.py @@ -2,7 +2,6 @@ class MenuSubItem: - def __init__( self, name: str, total_price: Optional[float], additional_price: Optional[float] ): diff --git a/src/event/migrations/0001_initial.py b/src/event/migrations/0001_initial.py index e5a3879..a8788ed 100644 --- a/src/event/migrations/0001_initial.py +++ b/src/event/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/event/migrations/0002_alter_event_id.py b/src/event/migrations/0002_alter_event_id.py index 77c5cbe..a18da95 100644 --- a/src/event/migrations/0002_alter_event_id.py +++ b/src/event/migrations/0002_alter_event_id.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("event", "0001_initial"), ] diff --git a/src/event/migrations/0003_alter_event_event_description.py b/src/event/migrations/0003_alter_event_event_description.py index ef70791..744b654 100644 --- a/src/event/migrations/0003_alter_event_event_description.py +++ b/src/event/migrations/0003_alter_event_event_description.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("event", "0002_alter_event_id"), ] diff --git a/src/event/migrations/0004_alter_event_event_description.py b/src/event/migrations/0004_alter_event_event_description.py index 4a8ca04..003ab07 100644 --- a/src/event/migrations/0004_alter_event_event_description.py +++ b/src/event/migrations/0004_alter_event_event_description.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("event", "0003_alter_event_event_description"), ] diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 643bdbc..21364a4 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -8,7 +8,6 @@ def __init__(self): self = self def generate_cafe_items(self, menu, json_eatery): - for json_item in json_eatery["diningItems"]: category_name = json_item["category"].strip() try: @@ -26,7 +25,6 @@ def generate_cafe_items(self, menu, json_eatery): def generate_dining_hall_items(self, menu, json_event, json_eatery): json_menus = json_event["menu"] for json_menu in json_menus: - category_name = json_menu["category"].strip() category_id = menu[category_name] diff --git a/src/item/migrations/0001_initial.py b/src/item/migrations/0001_initial.py index d9a19c6..7341f90 100644 --- a/src/item/migrations/0001_initial.py +++ b/src/item/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/item/migrations/0002_alter_item_id.py b/src/item/migrations/0002_alter_item_id.py index 8afff18..5c0ea2e 100644 --- a/src/item/migrations/0002_alter_item_id.py +++ b/src/item/migrations/0002_alter_item_id.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("item", "0001_initial"), ] diff --git a/src/manage.py b/src/manage.py index b469b27..f73fb68 100755 --- a/src/manage.py +++ b/src/manage.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys diff --git a/src/person/migrations/0001_initial.py b/src/person/migrations/0001_initial.py index f5a02be..cd090d5 100644 --- a/src/person/migrations/0001_initial.py +++ b/src/person/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/report/migrations/0001_initial.py b/src/report/migrations/0001_initial.py index fbfe01a..bc8a89c 100644 --- a/src/report/migrations/0001_initial.py +++ b/src/report/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/report/migrations/0002_report_netid.py b/src/report/migrations/0002_report_netid.py index 1bfc648..533fc58 100644 --- a/src/report/migrations/0002_report_netid.py +++ b/src/report/migrations/0002_report_netid.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("report", "0001_initial"), ] From b88d4b7c4423de9565d8b0627de34c6197039b37 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 24 Sep 2024 11:02:18 -0400 Subject: [PATCH 256/305] Adding sentry --- .pre-commit-config.yaml | 10 +++++++++- requirements.txt | 4 +++- src/category/migrations/0001_initial.py | 1 - src/category/migrations/0002_alter_category_id.py | 1 - src/eatery/controllers/update_eatery.py | 1 + src/eatery/datatype/EateryAlert.py | 1 - src/eatery/migrations/0001_initial.py | 1 - src/eatery/migrations/0002_alter_eatery_id.py | 1 - src/eatery/migrations/0003_alter_eatery_image_url.py | 1 - .../migrations/0004_alter_eatery_campus_area.py | 1 - .../migrations/0005_alter_eatery_campus_area.py | 1 - src/eatery/permissions.py | 1 - src/eatery/views.py | 12 ++++++------ .../management/commands/populate_models.py | 2 +- src/eatery_blue_backend/settings.py | 12 ++++++++++++ src/eatery_blue_backend/urls.py | 1 + .../controllers/update_models/CornellDiningEvents.py | 2 +- .../controllers/update_models/CornellDiningNow.py | 2 +- .../update_models/schedule/CornellDiningEvents.py | 2 +- src/event/datatype/Menu.py | 1 - src/event/datatype/MenuCategory.py | 1 - src/event/datatype/MenuItem.py | 1 - src/event/datatype/MenuItemSection.py | 1 - src/event/datatype/MenuSubItem.py | 1 - src/event/migrations/0001_initial.py | 1 - src/event/migrations/0002_alter_event_id.py | 1 - .../migrations/0003_alter_event_event_description.py | 1 - .../migrations/0004_alter_event_event_description.py | 1 - src/item/controllers/populate_item.py | 2 -- src/item/migrations/0001_initial.py | 1 - src/item/migrations/0002_alter_item_id.py | 1 - src/manage.py | 1 + src/person/migrations/0001_initial.py | 1 - src/report/migrations/0001_initial.py | 1 - src/report/migrations/0002_report_netid.py | 1 - 35 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2d273e..af043df 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,24 @@ repos: + # Updates all subsequent hooks - repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update rev: v0.5.1 hooks: - id: pre-commit-update + # Linter hook - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.7 hooks: - id: ruff args: [--fix] - - id: ruff-format + # Formatter hook + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.8.0 + hooks: + - id: black + + # Static code analysis hook - repo: https://github.com/PyCQA/bandit rev: 1.7.10 hooks: diff --git a/requirements.txt b/requirements.txt index f7c31ad..e6b7e10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ coreapi==2.3.3 coreschema==0.0.4 distlib==0.3.8 Django==4.0 +django-rest-swagger==2.2.0 djangorestframework==3.13.1 drf-yasg==1.21.7 filelock==3.15.4 @@ -42,11 +43,12 @@ PyYAML==6.0.2 requests==2.26.0 requests-oauthlib==1.3.0 rsa==4.8 +sentry-sdk==2.14.0 simplejson==3.19.3 six==1.16.0 sqlparse==0.4.2 tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 -urllib3==1.26.7 +urllib3==2.2.3 virtualenv==20.26.3 \ No newline at end of file diff --git a/src/category/migrations/0001_initial.py b/src/category/migrations/0001_initial.py index dca2274..038c293 100644 --- a/src/category/migrations/0001_initial.py +++ b/src/category/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/category/migrations/0002_alter_category_id.py b/src/category/migrations/0002_alter_category_id.py index cbb43d1..c10fb6d 100644 --- a/src/category/migrations/0002_alter_category_id.py +++ b/src/category/migrations/0002_alter_category_id.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("category", "0001_initial"), ] diff --git a/src/eatery/controllers/update_eatery.py b/src/eatery/controllers/update_eatery.py index e6dafd2..04c33c3 100644 --- a/src/eatery/controllers/update_eatery.py +++ b/src/eatery/controllers/update_eatery.py @@ -60,6 +60,7 @@ def upload_image(self, image): "bucket": os.environ["IMAGE_BUCKET"], "image": f"data:image/{extension};base64,{b64_encoded_image}", }, + timeout=10, ) try: diff --git a/src/eatery/datatype/EateryAlert.py b/src/eatery/datatype/EateryAlert.py index ffd307b..7dafc3c 100644 --- a/src/eatery/datatype/EateryAlert.py +++ b/src/eatery/datatype/EateryAlert.py @@ -1,5 +1,4 @@ class EateryAlert: - def __init__( self, id: int, description: str, start_timestamp: int, end_timestamp: int ): diff --git a/src/eatery/migrations/0001_initial.py b/src/eatery/migrations/0001_initial.py index 87246d5..30c531e 100644 --- a/src/eatery/migrations/0001_initial.py +++ b/src/eatery/migrations/0001_initial.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] diff --git a/src/eatery/migrations/0002_alter_eatery_id.py b/src/eatery/migrations/0002_alter_eatery_id.py index 2b70b83..0bcb3d6 100644 --- a/src/eatery/migrations/0002_alter_eatery_id.py +++ b/src/eatery/migrations/0002_alter_eatery_id.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eatery", "0001_initial"), ] diff --git a/src/eatery/migrations/0003_alter_eatery_image_url.py b/src/eatery/migrations/0003_alter_eatery_image_url.py index c276bb6..f975e32 100644 --- a/src/eatery/migrations/0003_alter_eatery_image_url.py +++ b/src/eatery/migrations/0003_alter_eatery_image_url.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eatery", "0002_alter_eatery_id"), ] diff --git a/src/eatery/migrations/0004_alter_eatery_campus_area.py b/src/eatery/migrations/0004_alter_eatery_campus_area.py index 77d43bd..619c925 100644 --- a/src/eatery/migrations/0004_alter_eatery_campus_area.py +++ b/src/eatery/migrations/0004_alter_eatery_campus_area.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eatery", "0003_alter_eatery_image_url"), ] diff --git a/src/eatery/migrations/0005_alter_eatery_campus_area.py b/src/eatery/migrations/0005_alter_eatery_campus_area.py index df3bfb1..1b219b1 100644 --- a/src/eatery/migrations/0005_alter_eatery_campus_area.py +++ b/src/eatery/migrations/0005_alter_eatery_campus_area.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eatery", "0004_alter_eatery_campus_area"), ] diff --git a/src/eatery/permissions.py b/src/eatery/permissions.py index 80a9513..1608cd2 100644 --- a/src/eatery/permissions.py +++ b/src/eatery/permissions.py @@ -2,7 +2,6 @@ class EateryPermission(permissions.BasePermission): - def has_permission(self, request, view): if view.action in ["list", "retrieve"]: return True diff --git a/src/eatery/views.py b/src/eatery/views.py index cb5eb8d..3c6f6db 100644 --- a/src/eatery/views.py +++ b/src/eatery/views.py @@ -42,12 +42,12 @@ def get_object(self): queryset = self.filter_queryset(self.get_queryset()) lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field - assert lookup_url_kwarg in self.kwargs, ( - "Expected view %s to be called with a URL keyword argument " - 'named "%s". Fix your URL conf, or set the `.lookup_field` ' - "attribute on the view correctly." - % (self.__class__.__name__, lookup_url_kwarg) - ) + # assert lookup_url_kwarg in self.kwargs, ( + # "Expected view %s to be called with a URL keyword argument " + # 'named "%s". Fix your URL conf, or set the `.lookup_field` ' + # "attribute on the view correctly." + # % (self.__class__.__name__, lookup_url_kwarg) + # ) # Uses the lookup_field attribute, which defaults to `pk` filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} obj = get_object_or_404(queryset, **filter_kwargs) diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index 952a733..1127546 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -22,7 +22,7 @@ def handle(self, *args, **kwargs): def get_json(self): try: - response = requests.get(CORNELL_DINING_URL) + response = requests.get(CORNELL_DINING_URL, timeout=10) except Exception as e: raise e if response.status_code <= 400: diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 22296dd..d3543d2 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -12,6 +12,18 @@ import os from pathlib import Path +import sentry_sdk + +sentry_sdk.init( + dsn="https://0f2451bfd9bf84c1858cf7319bd51425@o4507365244010496.ingest.us.sentry.io/4508008440856576", + # Set traces_sample_rate to 1.0 to capture 100% + # of transactions for tracing. + traces_sample_rate=1.0, + # Set profiles_sample_rate to 1.0 to profile 100% + # of sampled transactions. + # We recommend adjusting this value in production. + profiles_sample_rate=1.0, +) # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index d04ad7b..2393115 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -13,6 +13,7 @@ permission_classes=(permissions.AllowAny,), ) + urlpatterns = [ path("admin/", admin.site.urls), path("eatery/", include("eatery.urls")), diff --git a/src/event/controllers/update_models/CornellDiningEvents.py b/src/event/controllers/update_models/CornellDiningEvents.py index 40301b3..a3debc5 100644 --- a/src/event/controllers/update_models/CornellDiningEvents.py +++ b/src/event/controllers/update_models/CornellDiningEvents.py @@ -18,7 +18,7 @@ def __init__(self, eatery_id: EateryID, cache): def __call__(self, *args, **kwargs) -> list[Eatery]: if "eateries" not in self.cache: try: - response = requests.get(CORNELL_DINING_URL).json() + response = requests.get(CORNELL_DINING_URL, timeout=10).json() except Exception as e: raise e if response["status"] == "success": diff --git a/src/event/controllers/update_models/CornellDiningNow.py b/src/event/controllers/update_models/CornellDiningNow.py index 98693c7..d668a11 100644 --- a/src/event/controllers/update_models/CornellDiningNow.py +++ b/src/event/controllers/update_models/CornellDiningNow.py @@ -7,7 +7,7 @@ class CornellDiningNow(DfgNode): def __call__(self, *args, **kwargs) -> list[Eatery]: try: - response = requests.get(CORNELL_DINING_URL).json() + response = requests.get(CORNELL_DINING_URL, timeout=10).json() except Exception as e: raise e diff --git a/src/event/controllers/update_models/schedule/CornellDiningEvents.py b/src/event/controllers/update_models/schedule/CornellDiningEvents.py index 40301b3..a3debc5 100644 --- a/src/event/controllers/update_models/schedule/CornellDiningEvents.py +++ b/src/event/controllers/update_models/schedule/CornellDiningEvents.py @@ -18,7 +18,7 @@ def __init__(self, eatery_id: EateryID, cache): def __call__(self, *args, **kwargs) -> list[Eatery]: if "eateries" not in self.cache: try: - response = requests.get(CORNELL_DINING_URL).json() + response = requests.get(CORNELL_DINING_URL, timeout=10).json() except Exception as e: raise e if response["status"] == "success": diff --git a/src/event/datatype/Menu.py b/src/event/datatype/Menu.py index 6e77a77..b7375aa 100644 --- a/src/event/datatype/Menu.py +++ b/src/event/datatype/Menu.py @@ -2,7 +2,6 @@ class Menu: - def __init__(self, categories: list[MenuCategory]): self.categories = categories diff --git a/src/event/datatype/MenuCategory.py b/src/event/datatype/MenuCategory.py index e54262a..55770f9 100644 --- a/src/event/datatype/MenuCategory.py +++ b/src/event/datatype/MenuCategory.py @@ -2,7 +2,6 @@ class MenuCategory: - def __init__(self, category: str, items: list[MenuItem]): self.category = category self.items = items diff --git a/src/event/datatype/MenuItem.py b/src/event/datatype/MenuItem.py index 72b8b33..12a60ee 100644 --- a/src/event/datatype/MenuItem.py +++ b/src/event/datatype/MenuItem.py @@ -4,7 +4,6 @@ class MenuItem: - def __init__( self, name: str, diff --git a/src/event/datatype/MenuItemSection.py b/src/event/datatype/MenuItemSection.py index 1ac44af..fae1105 100644 --- a/src/event/datatype/MenuItemSection.py +++ b/src/event/datatype/MenuItemSection.py @@ -2,7 +2,6 @@ class MenuItemSection: - def __init__(self, name: str, subitems: list[MenuSubItem]): self.name = name self.subitems = subitems diff --git a/src/event/datatype/MenuSubItem.py b/src/event/datatype/MenuSubItem.py index 7c82c28..e236317 100644 --- a/src/event/datatype/MenuSubItem.py +++ b/src/event/datatype/MenuSubItem.py @@ -2,7 +2,6 @@ class MenuSubItem: - def __init__( self, name: str, total_price: Optional[float], additional_price: Optional[float] ): diff --git a/src/event/migrations/0001_initial.py b/src/event/migrations/0001_initial.py index e5a3879..a8788ed 100644 --- a/src/event/migrations/0001_initial.py +++ b/src/event/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/event/migrations/0002_alter_event_id.py b/src/event/migrations/0002_alter_event_id.py index 77c5cbe..a18da95 100644 --- a/src/event/migrations/0002_alter_event_id.py +++ b/src/event/migrations/0002_alter_event_id.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("event", "0001_initial"), ] diff --git a/src/event/migrations/0003_alter_event_event_description.py b/src/event/migrations/0003_alter_event_event_description.py index ef70791..744b654 100644 --- a/src/event/migrations/0003_alter_event_event_description.py +++ b/src/event/migrations/0003_alter_event_event_description.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("event", "0002_alter_event_id"), ] diff --git a/src/event/migrations/0004_alter_event_event_description.py b/src/event/migrations/0004_alter_event_event_description.py index 4a8ca04..003ab07 100644 --- a/src/event/migrations/0004_alter_event_event_description.py +++ b/src/event/migrations/0004_alter_event_event_description.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("event", "0003_alter_event_event_description"), ] diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 643bdbc..21364a4 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -8,7 +8,6 @@ def __init__(self): self = self def generate_cafe_items(self, menu, json_eatery): - for json_item in json_eatery["diningItems"]: category_name = json_item["category"].strip() try: @@ -26,7 +25,6 @@ def generate_cafe_items(self, menu, json_eatery): def generate_dining_hall_items(self, menu, json_event, json_eatery): json_menus = json_event["menu"] for json_menu in json_menus: - category_name = json_menu["category"].strip() category_id = menu[category_name] diff --git a/src/item/migrations/0001_initial.py b/src/item/migrations/0001_initial.py index d9a19c6..7341f90 100644 --- a/src/item/migrations/0001_initial.py +++ b/src/item/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/item/migrations/0002_alter_item_id.py b/src/item/migrations/0002_alter_item_id.py index 8afff18..5c0ea2e 100644 --- a/src/item/migrations/0002_alter_item_id.py +++ b/src/item/migrations/0002_alter_item_id.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("item", "0001_initial"), ] diff --git a/src/manage.py b/src/manage.py index b469b27..f73fb68 100755 --- a/src/manage.py +++ b/src/manage.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys diff --git a/src/person/migrations/0001_initial.py b/src/person/migrations/0001_initial.py index f5a02be..cd090d5 100644 --- a/src/person/migrations/0001_initial.py +++ b/src/person/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/report/migrations/0001_initial.py b/src/report/migrations/0001_initial.py index fbfe01a..bc8a89c 100644 --- a/src/report/migrations/0001_initial.py +++ b/src/report/migrations/0001_initial.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/src/report/migrations/0002_report_netid.py b/src/report/migrations/0002_report_netid.py index 1bfc648..533fc58 100644 --- a/src/report/migrations/0002_report_netid.py +++ b/src/report/migrations/0002_report_netid.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("report", "0001_initial"), ] From ecc14188e9967bb53a3c62cd096dde98f796c2db Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 24 Sep 2024 11:05:32 -0400 Subject: [PATCH 257/305] Fixing dependancy version issue --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e6b7e10..04f21db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,5 +50,5 @@ sqlparse==0.4.2 tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 -urllib3==2.2.3 +urllib3 virtualenv==20.26.3 \ No newline at end of file From aa953ae8bc63fea6e7264a258e83f9dca03f9fa2 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri Date: Tue, 24 Sep 2024 11:05:32 -0400 Subject: [PATCH 258/305] Fixing dependancy version issue --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e6b7e10..04f21db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -50,5 +50,5 @@ sqlparse==0.4.2 tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 -urllib3==2.2.3 +urllib3 virtualenv==20.26.3 \ No newline at end of file From 500c187bb135be70a2c2daf0eea23fbb29974ebe Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Fri, 11 Oct 2024 22:11:29 -0400 Subject: [PATCH 259/305] create/fix user model --- src/eatery_blue_backend/urls.py | 2 +- src/item/models.py | 2 +- src/user/admin.py | 1 - src/user/auth.py | 17 --------- src/user/migrations/0001_initial.py | 12 ++----- ...e_eateries_user_favorite_items_and_more.py | 35 +++++++++++++++++++ src/user/models.py | 10 +++--- src/user/serializers.py | 11 +----- src/user/tests.py | 3 ++ src/user/views.py | 6 ---- 10 files changed, 48 insertions(+), 51 deletions(-) delete mode 100644 src/user/auth.py create mode 100644 src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py create mode 100644 src/user/tests.py diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 0286eea..197ff29 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -8,5 +8,5 @@ path("item/", include("item.urls")), path("category/", include("category.urls")), path("report/", include("report.urls")), - path("users/", include("user.urls")), + path("user/", include("user.urls")), ] diff --git a/src/item/models.py b/src/item/models.py index 885d9ad..d0ac644 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -10,4 +10,4 @@ class Item(models.Model): base_price = models.FloatField(null=True, blank=True, default=0.0) def __str__(self): - return f"{self.name} ({self.category.name})" + return f"{self.name} ({self.category.id})" diff --git a/src/user/admin.py b/src/user/admin.py index 22c1186..8518016 100644 --- a/src/user/admin.py +++ b/src/user/admin.py @@ -1,5 +1,4 @@ from django.contrib import admin from user.models import User -# Register your models here. admin.site.register(User) diff --git a/src/user/auth.py b/src/user/auth.py deleted file mode 100644 index 23dcf4b..0000000 --- a/src/user/auth.py +++ /dev/null @@ -1,17 +0,0 @@ -# Import the Firebase service -import firebase_admin -from firebase_admin import auth -from firebase_admin import credentials -import os - -cred = credentials.Certificate(os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")) -default_app = firebase_admin.initialize_app(cred) - - -# Initialize the default app -# default_app = firebase_admin.initialize_app() -print(default_app.name) # "[DEFAULT]" - -uid = "DTkATsJbRJd5VO3E82V7M8HbTS83" -user = auth.get_user(uid) -print(user.email) diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py index 45e7ce5..1da6459 100644 --- a/src/user/migrations/0001_initial.py +++ b/src/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2024-03-27 18:21 +# Generated by Django 4.0 on 2024-10-09 16:00 from django.db import migrations, models @@ -8,8 +8,6 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('eatery', '0005_alter_eatery_campus_area'), - ('item', '0002_alter_item_id'), ] operations = [ @@ -17,13 +15,7 @@ class Migration(migrations.Migration): name='User', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('netid', models.CharField(default='User', max_length=40)), - ('token', models.CharField(default='User', max_length=40)), - ('name', models.CharField(default='User', max_length=40)), - ('is_admin', models.BooleanField(default=False)), - ('last_active', models.DateTimeField(auto_now=True)), - ('favorite_eateries', models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery')), - ('favorite_items', models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item')), + ('netid', models.CharField(blank=True, max_length=10, null=True)), ], ), ] diff --git a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py b/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py new file mode 100644 index 0000000..47c42cb --- /dev/null +++ b/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.0 on 2024-10-09 17:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0005_alter_eatery_campus_area'), + ('item', '0002_alter_item_id'), + ('user', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='favorite_eateries', + field=models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery'), + ), + migrations.AddField( + model_name='user', + name='favorite_items', + field=models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item'), + ), + migrations.AddField( + model_name='user', + name='is_admin', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='user', + name='name', + field=models.CharField(default='User', max_length=40), + ), + ] diff --git a/src/user/models.py b/src/user/models.py index bda4510..a6055e0 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -2,10 +2,7 @@ class User(models.Model): - userId = models.AutoField(primary_key=True) - token = models.CharField(max_length=40) - refresh_token = models.CharField(max_length=40) - netid = models.CharField(max_length=40, default="User") + netid = models.CharField(max_length=10, null=True, blank=True) name = models.CharField(max_length=40, default="User") favorite_items = models.ManyToManyField( "item.Item", related_name="favorited_by", blank=True @@ -14,4 +11,7 @@ class User(models.Model): "eatery.Eatery", related_name="favorited_by", blank=True ) is_admin = models.BooleanField(default=False) - last_active = models.DateTimeField(auto_now=True) + + def __str__(self): + return f'{self.netid}' + \ No newline at end of file diff --git a/src/user/serializers.py b/src/user/serializers.py index d40f3a8..6205785 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -5,13 +5,4 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = [ - "id", - "netid", - "token", - "name", - "favorite_items", - "favorite_eateries", - "is_admin", - "last_active", - ] + fields = ['id', 'name', 'netid', 'is_admin', 'favorite_eateries', 'favorite_items'] diff --git a/src/user/tests.py b/src/user/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/user/views.py b/src/user/views.py index 4afff42..6d41403 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -1,14 +1,8 @@ from rest_framework import viewsets from user.models import User from user.serializers import UserSerializer -from rest_framework.views import APIView class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer - - -class SSOViewSet(APIView): - def post(self, request): - pass From a7c16ac3cc670e7616ec6fa6d6a8bd544d101283 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Fri, 11 Oct 2024 22:11:29 -0400 Subject: [PATCH 260/305] create/fix user model --- src/eatery_blue_backend/urls.py | 2 +- src/item/models.py | 2 +- src/user/admin.py | 1 - src/user/auth.py | 17 --------- src/user/migrations/0001_initial.py | 12 ++----- ...e_eateries_user_favorite_items_and_more.py | 35 +++++++++++++++++++ src/user/models.py | 10 +++--- src/user/serializers.py | 11 +----- src/user/tests.py | 3 ++ src/user/views.py | 6 ---- 10 files changed, 48 insertions(+), 51 deletions(-) delete mode 100644 src/user/auth.py create mode 100644 src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py create mode 100644 src/user/tests.py diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 0286eea..197ff29 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -8,5 +8,5 @@ path("item/", include("item.urls")), path("category/", include("category.urls")), path("report/", include("report.urls")), - path("users/", include("user.urls")), + path("user/", include("user.urls")), ] diff --git a/src/item/models.py b/src/item/models.py index 885d9ad..d0ac644 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -10,4 +10,4 @@ class Item(models.Model): base_price = models.FloatField(null=True, blank=True, default=0.0) def __str__(self): - return f"{self.name} ({self.category.name})" + return f"{self.name} ({self.category.id})" diff --git a/src/user/admin.py b/src/user/admin.py index 22c1186..8518016 100644 --- a/src/user/admin.py +++ b/src/user/admin.py @@ -1,5 +1,4 @@ from django.contrib import admin from user.models import User -# Register your models here. admin.site.register(User) diff --git a/src/user/auth.py b/src/user/auth.py deleted file mode 100644 index 23dcf4b..0000000 --- a/src/user/auth.py +++ /dev/null @@ -1,17 +0,0 @@ -# Import the Firebase service -import firebase_admin -from firebase_admin import auth -from firebase_admin import credentials -import os - -cred = credentials.Certificate(os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")) -default_app = firebase_admin.initialize_app(cred) - - -# Initialize the default app -# default_app = firebase_admin.initialize_app() -print(default_app.name) # "[DEFAULT]" - -uid = "DTkATsJbRJd5VO3E82V7M8HbTS83" -user = auth.get_user(uid) -print(user.email) diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py index 45e7ce5..1da6459 100644 --- a/src/user/migrations/0001_initial.py +++ b/src/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2024-03-27 18:21 +# Generated by Django 4.0 on 2024-10-09 16:00 from django.db import migrations, models @@ -8,8 +8,6 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('eatery', '0005_alter_eatery_campus_area'), - ('item', '0002_alter_item_id'), ] operations = [ @@ -17,13 +15,7 @@ class Migration(migrations.Migration): name='User', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('netid', models.CharField(default='User', max_length=40)), - ('token', models.CharField(default='User', max_length=40)), - ('name', models.CharField(default='User', max_length=40)), - ('is_admin', models.BooleanField(default=False)), - ('last_active', models.DateTimeField(auto_now=True)), - ('favorite_eateries', models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery')), - ('favorite_items', models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item')), + ('netid', models.CharField(blank=True, max_length=10, null=True)), ], ), ] diff --git a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py b/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py new file mode 100644 index 0000000..47c42cb --- /dev/null +++ b/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.0 on 2024-10-09 17:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eatery', '0005_alter_eatery_campus_area'), + ('item', '0002_alter_item_id'), + ('user', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='favorite_eateries', + field=models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery'), + ), + migrations.AddField( + model_name='user', + name='favorite_items', + field=models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item'), + ), + migrations.AddField( + model_name='user', + name='is_admin', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='user', + name='name', + field=models.CharField(default='User', max_length=40), + ), + ] diff --git a/src/user/models.py b/src/user/models.py index bda4510..a6055e0 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -2,10 +2,7 @@ class User(models.Model): - userId = models.AutoField(primary_key=True) - token = models.CharField(max_length=40) - refresh_token = models.CharField(max_length=40) - netid = models.CharField(max_length=40, default="User") + netid = models.CharField(max_length=10, null=True, blank=True) name = models.CharField(max_length=40, default="User") favorite_items = models.ManyToManyField( "item.Item", related_name="favorited_by", blank=True @@ -14,4 +11,7 @@ class User(models.Model): "eatery.Eatery", related_name="favorited_by", blank=True ) is_admin = models.BooleanField(default=False) - last_active = models.DateTimeField(auto_now=True) + + def __str__(self): + return f'{self.netid}' + \ No newline at end of file diff --git a/src/user/serializers.py b/src/user/serializers.py index d40f3a8..6205785 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -5,13 +5,4 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = [ - "id", - "netid", - "token", - "name", - "favorite_items", - "favorite_eateries", - "is_admin", - "last_active", - ] + fields = ['id', 'name', 'netid', 'is_admin', 'favorite_eateries', 'favorite_items'] diff --git a/src/user/tests.py b/src/user/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/user/views.py b/src/user/views.py index 4afff42..6d41403 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -1,14 +1,8 @@ from rest_framework import viewsets from user.models import User from user.serializers import UserSerializer -from rest_framework.views import APIView class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer - - -class SSOViewSet(APIView): - def post(self, request): - pass From cde9a21d90aa614766cf43013e02e12ec001846e Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Wed, 16 Oct 2024 18:33:18 -0400 Subject: [PATCH 261/305] add endpoints for adding/removing favorite eateries/items --- src/user/migrations/0001_initial.py | 8 +++++- src/user/views.py | 43 ++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py index 1da6459..d89c658 100644 --- a/src/user/migrations/0001_initial.py +++ b/src/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2024-10-09 16:00 +# Generated by Django 4.0 on 2024-10-16 20:03 from django.db import migrations, models @@ -8,6 +8,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('item', '0002_alter_item_id'), + ('eatery', '0005_alter_eatery_campus_area'), ] operations = [ @@ -16,6 +18,10 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('netid', models.CharField(blank=True, max_length=10, null=True)), + ('name', models.CharField(default='User', max_length=40)), + ('is_admin', models.BooleanField(default=False)), + ('favorite_eateries', models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery')), + ('favorite_items', models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item')), ], ), ] diff --git a/src/user/views.py b/src/user/views.py index 6d41403..921488e 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -1,8 +1,49 @@ -from rest_framework import viewsets +from rest_framework import viewsets, status +from rest_framework.response import Response +from rest_framework.decorators import action from user.models import User from user.serializers import UserSerializer +from eatery.models import Eatery +from item.models import Item +from django.shortcuts import get_object_or_404 class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer + + @action(detail=True, methods=["post"], url_path="eatery/add") + def add_favorite_eatery(self, request, pk=None): + user = get_object_or_404(User, pk=pk) + eatery_id = request.data.get("eatery_id") + eatery = get_object_or_404(Eatery, id=eatery_id) + user.favorite_eateries.add(eatery) + user.save() + return Response({"status": "eatery added"}, status=status.HTTP_200_OK) + + @action(detail=True, methods=["post"], url_path="eatery/remove") + def remove_favorite_eatery(self, request, pk=None): + user = get_object_or_404(User, pk=pk) + eatery_id = request.data.get("eatery_id") + eatery = get_object_or_404(Eatery, id=eatery_id) + user.favorite_eateries.remove(eatery) + user.save() + return Response({"status": "eatery removed"}, status=status.HTTP_200_OK) + + @action(detail=True, methods=["post"], url_path="item/add") + def add_favorite_item(self, request, pk=None): + user = get_object_or_404(User, pk=pk) + item_id = request.data.get("item_id") + item = get_object_or_404(Item, id=item_id) + user.favorite_items.add(item) + user.save() + return Response({"status": "item added"}, status=status.HTTP_200_OK) + + @action(detail=True, methods=["post"], url_path="item/remove") + def remove_favorite_item(self, request, pk=None): + user = get_object_or_404(User, pk=pk) + item_id = request.data.get("item_id") + item = get_object_or_404(Item, id=item_id) + user.favorite_items.remove(item) + user.save() + return Response({"status": "item removed"}, status=status.HTTP_200_OK) From 417a3a49d771de4b7ba2ba6dcf31f166c1593aab Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Wed, 16 Oct 2024 18:33:18 -0400 Subject: [PATCH 262/305] add endpoints for adding/removing favorite eateries/items --- src/user/migrations/0001_initial.py | 8 +++++- src/user/views.py | 43 ++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py index 1da6459..d89c658 100644 --- a/src/user/migrations/0001_initial.py +++ b/src/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2024-10-09 16:00 +# Generated by Django 4.0 on 2024-10-16 20:03 from django.db import migrations, models @@ -8,6 +8,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('item', '0002_alter_item_id'), + ('eatery', '0005_alter_eatery_campus_area'), ] operations = [ @@ -16,6 +18,10 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('netid', models.CharField(blank=True, max_length=10, null=True)), + ('name', models.CharField(default='User', max_length=40)), + ('is_admin', models.BooleanField(default=False)), + ('favorite_eateries', models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery')), + ('favorite_items', models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item')), ], ), ] diff --git a/src/user/views.py b/src/user/views.py index 6d41403..921488e 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -1,8 +1,49 @@ -from rest_framework import viewsets +from rest_framework import viewsets, status +from rest_framework.response import Response +from rest_framework.decorators import action from user.models import User from user.serializers import UserSerializer +from eatery.models import Eatery +from item.models import Item +from django.shortcuts import get_object_or_404 class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer + + @action(detail=True, methods=["post"], url_path="eatery/add") + def add_favorite_eatery(self, request, pk=None): + user = get_object_or_404(User, pk=pk) + eatery_id = request.data.get("eatery_id") + eatery = get_object_or_404(Eatery, id=eatery_id) + user.favorite_eateries.add(eatery) + user.save() + return Response({"status": "eatery added"}, status=status.HTTP_200_OK) + + @action(detail=True, methods=["post"], url_path="eatery/remove") + def remove_favorite_eatery(self, request, pk=None): + user = get_object_or_404(User, pk=pk) + eatery_id = request.data.get("eatery_id") + eatery = get_object_or_404(Eatery, id=eatery_id) + user.favorite_eateries.remove(eatery) + user.save() + return Response({"status": "eatery removed"}, status=status.HTTP_200_OK) + + @action(detail=True, methods=["post"], url_path="item/add") + def add_favorite_item(self, request, pk=None): + user = get_object_or_404(User, pk=pk) + item_id = request.data.get("item_id") + item = get_object_or_404(Item, id=item_id) + user.favorite_items.add(item) + user.save() + return Response({"status": "item added"}, status=status.HTTP_200_OK) + + @action(detail=True, methods=["post"], url_path="item/remove") + def remove_favorite_item(self, request, pk=None): + user = get_object_or_404(User, pk=pk) + item_id = request.data.get("item_id") + item = get_object_or_404(Item, id=item_id) + user.favorite_items.remove(item) + user.save() + return Response({"status": "item removed"}, status=status.HTTP_200_OK) From 47913ff9d63a2e270b1544ca966705550fbf6bcb Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Sat, 26 Oct 2024 20:26:20 -0400 Subject: [PATCH 263/305] address PR comments --- src/eatery_blue_backend/urls.py | 1 + src/user/views.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index a7c7e81..0c15f28 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -22,4 +22,5 @@ path("category/", include("category.urls")), path("report/", include("report.urls")), path("user/", include("user.urls")), + path("docs/", schema_view.with_ui("swagger", cache_timeout=0)), ] diff --git a/src/user/views.py b/src/user/views.py index 921488e..702b5c2 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -19,7 +19,8 @@ def add_favorite_eatery(self, request, pk=None): eatery = get_object_or_404(Eatery, id=eatery_id) user.favorite_eateries.add(eatery) user.save() - return Response({"status": "eatery added"}, status=status.HTTP_200_OK) + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) @action(detail=True, methods=["post"], url_path="eatery/remove") def remove_favorite_eatery(self, request, pk=None): @@ -28,7 +29,8 @@ def remove_favorite_eatery(self, request, pk=None): eatery = get_object_or_404(Eatery, id=eatery_id) user.favorite_eateries.remove(eatery) user.save() - return Response({"status": "eatery removed"}, status=status.HTTP_200_OK) + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) @action(detail=True, methods=["post"], url_path="item/add") def add_favorite_item(self, request, pk=None): @@ -37,7 +39,8 @@ def add_favorite_item(self, request, pk=None): item = get_object_or_404(Item, id=item_id) user.favorite_items.add(item) user.save() - return Response({"status": "item added"}, status=status.HTTP_200_OK) + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) @action(detail=True, methods=["post"], url_path="item/remove") def remove_favorite_item(self, request, pk=None): @@ -46,4 +49,5 @@ def remove_favorite_item(self, request, pk=None): item = get_object_or_404(Item, id=item_id) user.favorite_items.remove(item) user.save() - return Response({"status": "item removed"}, status=status.HTTP_200_OK) + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) From e87687d35e171ea0e268d027de743a1df475bf77 Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Sat, 26 Oct 2024 20:26:20 -0400 Subject: [PATCH 264/305] address PR comments --- src/eatery_blue_backend/urls.py | 1 + src/user/views.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index a7c7e81..0c15f28 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -22,4 +22,5 @@ path("category/", include("category.urls")), path("report/", include("report.urls")), path("user/", include("user.urls")), + path("docs/", schema_view.with_ui("swagger", cache_timeout=0)), ] diff --git a/src/user/views.py b/src/user/views.py index 921488e..702b5c2 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -19,7 +19,8 @@ def add_favorite_eatery(self, request, pk=None): eatery = get_object_or_404(Eatery, id=eatery_id) user.favorite_eateries.add(eatery) user.save() - return Response({"status": "eatery added"}, status=status.HTTP_200_OK) + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) @action(detail=True, methods=["post"], url_path="eatery/remove") def remove_favorite_eatery(self, request, pk=None): @@ -28,7 +29,8 @@ def remove_favorite_eatery(self, request, pk=None): eatery = get_object_or_404(Eatery, id=eatery_id) user.favorite_eateries.remove(eatery) user.save() - return Response({"status": "eatery removed"}, status=status.HTTP_200_OK) + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) @action(detail=True, methods=["post"], url_path="item/add") def add_favorite_item(self, request, pk=None): @@ -37,7 +39,8 @@ def add_favorite_item(self, request, pk=None): item = get_object_or_404(Item, id=item_id) user.favorite_items.add(item) user.save() - return Response({"status": "item added"}, status=status.HTTP_200_OK) + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) @action(detail=True, methods=["post"], url_path="item/remove") def remove_favorite_item(self, request, pk=None): @@ -46,4 +49,5 @@ def remove_favorite_item(self, request, pk=None): item = get_object_or_404(Item, id=item_id) user.favorite_items.remove(item) user.save() - return Response({"status": "item removed"}, status=status.HTTP_200_OK) + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) From 43fb83651f9a27c3f354d667d8673aa9069ad2a8 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 5 Nov 2024 13:51:45 -0500 Subject: [PATCH 265/305] implement user auth and login routes --- requirements.txt | 20 ++---- src/eatery_blue_backend/settings.py | 1 + src/eatery_blue_backend/urls.py | 1 + src/user/migrations/0001_initial.py | 2 +- ...in_remove_user_name_user_email_and_more.py | 41 +++++++++++ ...e_eateries_user_favorite_items_and_more.py | 35 ---------- src/user/models.py | 6 +- src/user/serializers.py | 2 +- src/user/urls.py | 1 + src/user/views.py | 68 +++++++++++++++++++ src/user_session/__init__.py | 0 src/user_session/admin.py | 3 + src/user_session/apps.py | 5 ++ src/user_session/migrations/0001_initial.py | 25 +++++++ src/user_session/migrations/__init__.py | 0 src/user_session/models.py | 13 ++++ src/user_session/serializers.py | 7 ++ src/user_session/tests.py | 3 + src/user_session/urls.py | 11 +++ src/user_session/views.py | 8 +++ 20 files changed, 199 insertions(+), 53 deletions(-) create mode 100644 src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py delete mode 100644 src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py create mode 100644 src/user_session/__init__.py create mode 100644 src/user_session/admin.py create mode 100644 src/user_session/apps.py create mode 100644 src/user_session/migrations/0001_initial.py create mode 100644 src/user_session/migrations/__init__.py create mode 100644 src/user_session/models.py create mode 100644 src/user_session/serializers.py create mode 100644 src/user_session/tests.py create mode 100644 src/user_session/urls.py create mode 100644 src/user_session/views.py diff --git a/requirements.txt b/requirements.txt index 6169f6f..f9c09d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,19 +12,12 @@ django-rest-swagger==2.2.0 djangorestframework==3.13.1 drf-yasg==1.21.7 filelock==3.15.4 -google-api-core>=2.0.0,<3.0.0 +google-api-core==2.3.2 google-api-python-client==2.33.0 -google-auth==2.29.0 +google-auth==2.3.3 google-auth-httplib2==0.1.0 google-auth-oauthlib==0.4.6 -google-cloud-core==2.4.1 -google-cloud-firestore>=2.15.0,<3.0.0 -google-cloud-storage==2.16.0 -google-crc32c==1.5.0 -google-resumable-media==2.7.0 -googleapis-common-protos==1.63.0 -grpcio==1.62.1 -grpcio-status==1.62.1 +googleapis-common-protos==1.54.0 httplib2==0.20.2 identify==2.6.0 idna==3.3 @@ -40,12 +33,10 @@ packaging==24.1 pathspec==0.9.0 platformdirs==4.2.2 pre-commit==3.8.0 -protobuf>=4.21.6,<5.0.0 +protobuf==3.19.1 psycopg2-binary==2.9.3 pyasn1==0.4.8 pyasn1-modules==0.2.8 -pycparser==2.22 -PyJWT==2.8.0 pyparsing==3.0.6 pytz==2021.3 PyYAML==6.0.2 @@ -59,5 +50,6 @@ sqlparse==0.4.2 tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 +urllib3 virtualenv==20.26.3 -urllib3<1.27,>=1.26.7 +google-auth \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index f08bf68..819c223 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -61,6 +61,7 @@ "item", "category", "user", + "user_session", # Third party "rest_framework", ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 0c15f28..0ea46a4 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -22,5 +22,6 @@ path("category/", include("category.urls")), path("report/", include("report.urls")), path("user/", include("user.urls")), + path("user-session/", include("user_session.urls")), path("docs/", schema_view.with_ui("swagger", cache_timeout=0)), ] diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py index d89c658..2abef68 100644 --- a/src/user/migrations/0001_initial.py +++ b/src/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2024-10-16 20:03 +# Generated by Django 4.0 on 2024-11-04 17:02 from django.db import migrations, models diff --git a/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py b/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py new file mode 100644 index 0000000..0556a0f --- /dev/null +++ b/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 4.0 on 2024-11-04 19:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='is_admin', + ), + migrations.RemoveField( + model_name='user', + name='name', + ), + migrations.AddField( + model_name='user', + name='email', + field=models.EmailField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='user', + name='family_name', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AddField( + model_name='user', + name='given_name', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AddField( + model_name='user', + name='google_id', + field=models.CharField(blank=True, max_length=50, null=True, unique=True), + ), + ] diff --git a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py b/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py deleted file mode 100644 index 47c42cb..0000000 --- a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 4.0 on 2024-10-09 17:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eatery', '0005_alter_eatery_campus_area'), - ('item', '0002_alter_item_id'), - ('user', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='favorite_eateries', - field=models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery'), - ), - migrations.AddField( - model_name='user', - name='favorite_items', - field=models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item'), - ), - migrations.AddField( - model_name='user', - name='is_admin', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='user', - name='name', - field=models.CharField(default='User', max_length=40), - ), - ] diff --git a/src/user/models.py b/src/user/models.py index a6055e0..615a5bb 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -3,14 +3,16 @@ class User(models.Model): netid = models.CharField(max_length=10, null=True, blank=True) - name = models.CharField(max_length=40, default="User") + given_name = models.CharField(max_length=30, null=True, blank=True) + family_name = models.CharField(max_length=30, null=True, blank=True) + google_id = models.CharField(max_length=50, unique=True, null=True, blank=True) + email = models.EmailField(max_length=255, null=True, blank=True) favorite_items = models.ManyToManyField( "item.Item", related_name="favorited_by", blank=True ) favorite_eateries = models.ManyToManyField( "eatery.Eatery", related_name="favorited_by", blank=True ) - is_admin = models.BooleanField(default=False) def __str__(self): return f'{self.netid}' diff --git a/src/user/serializers.py b/src/user/serializers.py index 6205785..45447d7 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -5,4 +5,4 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ['id', 'name', 'netid', 'is_admin', 'favorite_eateries', 'favorite_items'] + fields = ['id', 'given_name', 'family_name', 'netid', 'google_id', 'favorite_eateries', 'favorite_items'] diff --git a/src/user/urls.py b/src/user/urls.py index dbf70f2..2201a52 100644 --- a/src/user/urls.py +++ b/src/user/urls.py @@ -1,4 +1,5 @@ from django.urls import path, include +from django.views.decorators.http import require_POST from user.views import UserViewSet from rest_framework.routers import DefaultRouter diff --git a/src/user/views.py b/src/user/views.py index 702b5c2..8fd9850 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -6,6 +6,17 @@ from eatery.models import Eatery from item.models import Item from django.shortcuts import get_object_or_404 +from rest_framework.views import APIView +from rest_framework.response import Response +from django.conf import settings +from google.oauth2 import id_token +from google.auth.transport import requests +from user.models import User +from user_session.models import UserSession +import secrets +import os + + class UserViewSet(viewsets.ModelViewSet): @@ -51,3 +62,60 @@ def remove_favorite_item(self, request, pk=None): user.save() user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) + + @action(detail=False, methods=['post'], url_path='login') + def login(self, request): + id_token_str = request.data.get('idToken') + user = request.data.get('user') + + if not id_token_str or not user: + return Response({'error': 'Invalid request'}, status=status.HTTP_400_BAD_REQUEST) + + try: + # verifies token + idinfo = id_token.verify_oauth2_token(id_token_str, requests.Request(), os.getenv('GOOGLE_CLIENT_ID')) + + # ensures token is from Google + if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: + return Response({'error': 'Invalid token issuer'}, status=status.HTTP_401_UNAUTHORIZED) + + userid = idinfo['sub'] + email = idinfo['email'] + + # ensures email is Cornell email + if not email.endswith('@cornell.edu'): + return Response({'error': 'Non-Cornell email used'}, status=status.HTTP_401_UNAUTHORIZED) + + # creates user if not exists + user = User.objects.get_or_create( + google_id=userid, + defaults={ + 'email': email, + 'given_name': idinfo.get('given_name', ''), + 'family_name': idinfo.get('family_name', ''), + 'netid': email.split('@')[0], + } + ) + + # creates session token and makes new user session + session_token = secrets.token_hex(20) + UserSession.objects.create(user=user) + + return Response({'session_token': session_token}, status=status.HTTP_200_OK) + + except ValueError: + return Response({'error': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST) + + + @action(detail=False, methods=['post'], url_path='logout') + def logout(self, request): + session_token = request.data.get('session_token') + if not session_token: + return Response({'error': 'Session token required'}, status=status.HTTP_400_BAD_REQUEST) + try: + # gets and deletes user session + user_session = UserSession.objects.get(session_token=session_token) + user_session.delete() + return Response({'message': 'Logged out successfully'}, status=status.HTTP_200_OK) + except UserSession.DoesNotExist: + return Response({'error': 'Invalid session token'}, status=status.HTTP_400_BAD_REQUEST) diff --git a/src/user_session/__init__.py b/src/user_session/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user_session/admin.py b/src/user_session/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/src/user_session/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/user_session/apps.py b/src/user_session/apps.py new file mode 100644 index 0000000..6756df9 --- /dev/null +++ b/src/user_session/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class UserSessionConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user_session' diff --git a/src/user_session/migrations/0001_initial.py b/src/user_session/migrations/0001_initial.py new file mode 100644 index 0000000..6ba12b2 --- /dev/null +++ b/src/user_session/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0 on 2024-11-04 17:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('user', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='UserSession', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('session_token', models.CharField(max_length=40)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_sessions', to='user.user')), + ], + ), + ] diff --git a/src/user_session/migrations/__init__.py b/src/user_session/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user_session/models.py b/src/user_session/models.py new file mode 100644 index 0000000..bcb664d --- /dev/null +++ b/src/user_session/models.py @@ -0,0 +1,13 @@ +from django.db import models +from user.models import User + +class UserSession(models.Model): + id = models.AutoField(primary_key=True) + user = models.ForeignKey( + User, related_name="user_sessions", on_delete=models.CASCADE + ) + session_token = models.CharField(max_length=40) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f'{self.user.netid} - {self.session_token}' \ No newline at end of file diff --git a/src/user_session/serializers.py b/src/user_session/serializers.py new file mode 100644 index 0000000..7b232da --- /dev/null +++ b/src/user_session/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from user_session.models import UserSession + +class UserSessionSerializer(serializers.ModelSerializer): + class Meta: + model = UserSession + fields = ['id', 'user', 'session_token', 'created_at'] \ No newline at end of file diff --git a/src/user_session/tests.py b/src/user_session/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/user_session/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/user_session/urls.py b/src/user_session/urls.py new file mode 100644 index 0000000..baa6cc0 --- /dev/null +++ b/src/user_session/urls.py @@ -0,0 +1,11 @@ +from django.urls import path, include +from user_session.views import UserSessionViewSet + +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register("", UserSessionViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] \ No newline at end of file diff --git a/src/user_session/views.py b/src/user_session/views.py new file mode 100644 index 0000000..d0b271a --- /dev/null +++ b/src/user_session/views.py @@ -0,0 +1,8 @@ +from rest_framework import viewsets +from user_session.models import UserSession +from user_session.serializers import UserSessionSerializer + + +class UserSessionViewSet(viewsets.ModelViewSet): + queryset = UserSession.objects.all() + serializer_class = UserSessionSerializer From 517e802228e91209cf6eca8e1f1c665375917078 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 5 Nov 2024 13:51:45 -0500 Subject: [PATCH 266/305] implement user auth and login routes --- requirements.txt | 20 ++---- src/eatery_blue_backend/settings.py | 1 + src/eatery_blue_backend/urls.py | 1 + src/user/migrations/0001_initial.py | 2 +- ...in_remove_user_name_user_email_and_more.py | 41 +++++++++++ ...e_eateries_user_favorite_items_and_more.py | 35 ---------- src/user/models.py | 6 +- src/user/serializers.py | 2 +- src/user/urls.py | 1 + src/user/views.py | 68 +++++++++++++++++++ src/user_session/__init__.py | 0 src/user_session/admin.py | 3 + src/user_session/apps.py | 5 ++ src/user_session/migrations/0001_initial.py | 25 +++++++ src/user_session/migrations/__init__.py | 0 src/user_session/models.py | 13 ++++ src/user_session/serializers.py | 7 ++ src/user_session/tests.py | 3 + src/user_session/urls.py | 11 +++ src/user_session/views.py | 8 +++ 20 files changed, 199 insertions(+), 53 deletions(-) create mode 100644 src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py delete mode 100644 src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py create mode 100644 src/user_session/__init__.py create mode 100644 src/user_session/admin.py create mode 100644 src/user_session/apps.py create mode 100644 src/user_session/migrations/0001_initial.py create mode 100644 src/user_session/migrations/__init__.py create mode 100644 src/user_session/models.py create mode 100644 src/user_session/serializers.py create mode 100644 src/user_session/tests.py create mode 100644 src/user_session/urls.py create mode 100644 src/user_session/views.py diff --git a/requirements.txt b/requirements.txt index 6169f6f..f9c09d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,19 +12,12 @@ django-rest-swagger==2.2.0 djangorestframework==3.13.1 drf-yasg==1.21.7 filelock==3.15.4 -google-api-core>=2.0.0,<3.0.0 +google-api-core==2.3.2 google-api-python-client==2.33.0 -google-auth==2.29.0 +google-auth==2.3.3 google-auth-httplib2==0.1.0 google-auth-oauthlib==0.4.6 -google-cloud-core==2.4.1 -google-cloud-firestore>=2.15.0,<3.0.0 -google-cloud-storage==2.16.0 -google-crc32c==1.5.0 -google-resumable-media==2.7.0 -googleapis-common-protos==1.63.0 -grpcio==1.62.1 -grpcio-status==1.62.1 +googleapis-common-protos==1.54.0 httplib2==0.20.2 identify==2.6.0 idna==3.3 @@ -40,12 +33,10 @@ packaging==24.1 pathspec==0.9.0 platformdirs==4.2.2 pre-commit==3.8.0 -protobuf>=4.21.6,<5.0.0 +protobuf==3.19.1 psycopg2-binary==2.9.3 pyasn1==0.4.8 pyasn1-modules==0.2.8 -pycparser==2.22 -PyJWT==2.8.0 pyparsing==3.0.6 pytz==2021.3 PyYAML==6.0.2 @@ -59,5 +50,6 @@ sqlparse==0.4.2 tomli==1.2.3 typing_extensions==4.0.1 uritemplate==4.1.1 +urllib3 virtualenv==20.26.3 -urllib3<1.27,>=1.26.7 +google-auth \ No newline at end of file diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index f08bf68..819c223 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -61,6 +61,7 @@ "item", "category", "user", + "user_session", # Third party "rest_framework", ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 0c15f28..0ea46a4 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -22,5 +22,6 @@ path("category/", include("category.urls")), path("report/", include("report.urls")), path("user/", include("user.urls")), + path("user-session/", include("user_session.urls")), path("docs/", schema_view.with_ui("swagger", cache_timeout=0)), ] diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py index d89c658..2abef68 100644 --- a/src/user/migrations/0001_initial.py +++ b/src/user/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2024-10-16 20:03 +# Generated by Django 4.0 on 2024-11-04 17:02 from django.db import migrations, models diff --git a/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py b/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py new file mode 100644 index 0000000..0556a0f --- /dev/null +++ b/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 4.0 on 2024-11-04 19:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='is_admin', + ), + migrations.RemoveField( + model_name='user', + name='name', + ), + migrations.AddField( + model_name='user', + name='email', + field=models.EmailField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='user', + name='family_name', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AddField( + model_name='user', + name='given_name', + field=models.CharField(blank=True, max_length=30, null=True), + ), + migrations.AddField( + model_name='user', + name='google_id', + field=models.CharField(blank=True, max_length=50, null=True, unique=True), + ), + ] diff --git a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py b/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py deleted file mode 100644 index 47c42cb..0000000 --- a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 4.0 on 2024-10-09 17:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eatery', '0005_alter_eatery_campus_area'), - ('item', '0002_alter_item_id'), - ('user', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='favorite_eateries', - field=models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery'), - ), - migrations.AddField( - model_name='user', - name='favorite_items', - field=models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item'), - ), - migrations.AddField( - model_name='user', - name='is_admin', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='user', - name='name', - field=models.CharField(default='User', max_length=40), - ), - ] diff --git a/src/user/models.py b/src/user/models.py index a6055e0..615a5bb 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -3,14 +3,16 @@ class User(models.Model): netid = models.CharField(max_length=10, null=True, blank=True) - name = models.CharField(max_length=40, default="User") + given_name = models.CharField(max_length=30, null=True, blank=True) + family_name = models.CharField(max_length=30, null=True, blank=True) + google_id = models.CharField(max_length=50, unique=True, null=True, blank=True) + email = models.EmailField(max_length=255, null=True, blank=True) favorite_items = models.ManyToManyField( "item.Item", related_name="favorited_by", blank=True ) favorite_eateries = models.ManyToManyField( "eatery.Eatery", related_name="favorited_by", blank=True ) - is_admin = models.BooleanField(default=False) def __str__(self): return f'{self.netid}' diff --git a/src/user/serializers.py b/src/user/serializers.py index 6205785..45447d7 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -5,4 +5,4 @@ class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ['id', 'name', 'netid', 'is_admin', 'favorite_eateries', 'favorite_items'] + fields = ['id', 'given_name', 'family_name', 'netid', 'google_id', 'favorite_eateries', 'favorite_items'] diff --git a/src/user/urls.py b/src/user/urls.py index dbf70f2..2201a52 100644 --- a/src/user/urls.py +++ b/src/user/urls.py @@ -1,4 +1,5 @@ from django.urls import path, include +from django.views.decorators.http import require_POST from user.views import UserViewSet from rest_framework.routers import DefaultRouter diff --git a/src/user/views.py b/src/user/views.py index 702b5c2..8fd9850 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -6,6 +6,17 @@ from eatery.models import Eatery from item.models import Item from django.shortcuts import get_object_or_404 +from rest_framework.views import APIView +from rest_framework.response import Response +from django.conf import settings +from google.oauth2 import id_token +from google.auth.transport import requests +from user.models import User +from user_session.models import UserSession +import secrets +import os + + class UserViewSet(viewsets.ModelViewSet): @@ -51,3 +62,60 @@ def remove_favorite_item(self, request, pk=None): user.save() user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) + + @action(detail=False, methods=['post'], url_path='login') + def login(self, request): + id_token_str = request.data.get('idToken') + user = request.data.get('user') + + if not id_token_str or not user: + return Response({'error': 'Invalid request'}, status=status.HTTP_400_BAD_REQUEST) + + try: + # verifies token + idinfo = id_token.verify_oauth2_token(id_token_str, requests.Request(), os.getenv('GOOGLE_CLIENT_ID')) + + # ensures token is from Google + if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: + return Response({'error': 'Invalid token issuer'}, status=status.HTTP_401_UNAUTHORIZED) + + userid = idinfo['sub'] + email = idinfo['email'] + + # ensures email is Cornell email + if not email.endswith('@cornell.edu'): + return Response({'error': 'Non-Cornell email used'}, status=status.HTTP_401_UNAUTHORIZED) + + # creates user if not exists + user = User.objects.get_or_create( + google_id=userid, + defaults={ + 'email': email, + 'given_name': idinfo.get('given_name', ''), + 'family_name': idinfo.get('family_name', ''), + 'netid': email.split('@')[0], + } + ) + + # creates session token and makes new user session + session_token = secrets.token_hex(20) + UserSession.objects.create(user=user) + + return Response({'session_token': session_token}, status=status.HTTP_200_OK) + + except ValueError: + return Response({'error': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST) + + + @action(detail=False, methods=['post'], url_path='logout') + def logout(self, request): + session_token = request.data.get('session_token') + if not session_token: + return Response({'error': 'Session token required'}, status=status.HTTP_400_BAD_REQUEST) + try: + # gets and deletes user session + user_session = UserSession.objects.get(session_token=session_token) + user_session.delete() + return Response({'message': 'Logged out successfully'}, status=status.HTTP_200_OK) + except UserSession.DoesNotExist: + return Response({'error': 'Invalid session token'}, status=status.HTTP_400_BAD_REQUEST) diff --git a/src/user_session/__init__.py b/src/user_session/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user_session/admin.py b/src/user_session/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/src/user_session/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/user_session/apps.py b/src/user_session/apps.py new file mode 100644 index 0000000..6756df9 --- /dev/null +++ b/src/user_session/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class UserSessionConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user_session' diff --git a/src/user_session/migrations/0001_initial.py b/src/user_session/migrations/0001_initial.py new file mode 100644 index 0000000..6ba12b2 --- /dev/null +++ b/src/user_session/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 4.0 on 2024-11-04 17:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('user', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='UserSession', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('session_token', models.CharField(max_length=40)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_sessions', to='user.user')), + ], + ), + ] diff --git a/src/user_session/migrations/__init__.py b/src/user_session/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/user_session/models.py b/src/user_session/models.py new file mode 100644 index 0000000..bcb664d --- /dev/null +++ b/src/user_session/models.py @@ -0,0 +1,13 @@ +from django.db import models +from user.models import User + +class UserSession(models.Model): + id = models.AutoField(primary_key=True) + user = models.ForeignKey( + User, related_name="user_sessions", on_delete=models.CASCADE + ) + session_token = models.CharField(max_length=40) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f'{self.user.netid} - {self.session_token}' \ No newline at end of file diff --git a/src/user_session/serializers.py b/src/user_session/serializers.py new file mode 100644 index 0000000..7b232da --- /dev/null +++ b/src/user_session/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from user_session.models import UserSession + +class UserSessionSerializer(serializers.ModelSerializer): + class Meta: + model = UserSession + fields = ['id', 'user', 'session_token', 'created_at'] \ No newline at end of file diff --git a/src/user_session/tests.py b/src/user_session/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/user_session/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/user_session/urls.py b/src/user_session/urls.py new file mode 100644 index 0000000..baa6cc0 --- /dev/null +++ b/src/user_session/urls.py @@ -0,0 +1,11 @@ +from django.urls import path, include +from user_session.views import UserSessionViewSet + +from rest_framework.routers import DefaultRouter + +router = DefaultRouter() +router.register("", UserSessionViewSet) + +urlpatterns = [ + path("", include(router.urls)), +] \ No newline at end of file diff --git a/src/user_session/views.py b/src/user_session/views.py new file mode 100644 index 0000000..d0b271a --- /dev/null +++ b/src/user_session/views.py @@ -0,0 +1,8 @@ +from rest_framework import viewsets +from user_session.models import UserSession +from user_session.serializers import UserSessionSerializer + + +class UserSessionViewSet(viewsets.ModelViewSet): + queryset = UserSession.objects.all() + serializer_class = UserSessionSerializer From 1461f6f1309bb635a943e62a7bc63ede3102ab51 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 5 Nov 2024 13:52:08 -0500 Subject: [PATCH 267/305] bug fix for populating --- src/eatery/controllers/populate_eatery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index 3961d7d..712e245 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -71,7 +71,7 @@ def add_eatery_store(self): for json_obj in json_objs: try: object = Eatery.objects.get(id=int(json_obj["id"])) - except object.DoesNotExist: + except Eatery.DoesNotExist: """ Create a new Eatery object """ From cecdc2411865d66e66f9f1c4df05871c03794761 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 5 Nov 2024 13:52:08 -0500 Subject: [PATCH 268/305] bug fix for populating --- src/eatery/controllers/populate_eatery.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index 3961d7d..712e245 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -71,7 +71,7 @@ def add_eatery_store(self): for json_obj in json_objs: try: object = Eatery.objects.get(id=int(json_obj["id"])) - except object.DoesNotExist: + except Eatery.DoesNotExist: """ Create a new Eatery object """ From 73fd5b8bb7895d29888857d25ece7fce09cccece Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Wed, 6 Nov 2024 17:42:57 -0500 Subject: [PATCH 269/305] fix migrations --- ...in_remove_user_name_user_email_and_more.py | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py diff --git a/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py b/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py deleted file mode 100644 index 0556a0f..0000000 --- a/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 4.0 on 2024-11-04 19:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('user', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='user', - name='is_admin', - ), - migrations.RemoveField( - model_name='user', - name='name', - ), - migrations.AddField( - model_name='user', - name='email', - field=models.EmailField(blank=True, max_length=255, null=True), - ), - migrations.AddField( - model_name='user', - name='family_name', - field=models.CharField(blank=True, max_length=30, null=True), - ), - migrations.AddField( - model_name='user', - name='given_name', - field=models.CharField(blank=True, max_length=30, null=True), - ), - migrations.AddField( - model_name='user', - name='google_id', - field=models.CharField(blank=True, max_length=50, null=True, unique=True), - ), - ] From 4e5f2c3abb2877e7e9ed3cbcfc6573038613da97 Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Wed, 6 Nov 2024 17:42:57 -0500 Subject: [PATCH 270/305] fix migrations --- ...in_remove_user_name_user_email_and_more.py | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py diff --git a/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py b/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py deleted file mode 100644 index 0556a0f..0000000 --- a/src/user/migrations/0002_remove_user_is_admin_remove_user_name_user_email_and_more.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 4.0 on 2024-11-04 19:24 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('user', '0001_initial'), - ] - - operations = [ - migrations.RemoveField( - model_name='user', - name='is_admin', - ), - migrations.RemoveField( - model_name='user', - name='name', - ), - migrations.AddField( - model_name='user', - name='email', - field=models.EmailField(blank=True, max_length=255, null=True), - ), - migrations.AddField( - model_name='user', - name='family_name', - field=models.CharField(blank=True, max_length=30, null=True), - ), - migrations.AddField( - model_name='user', - name='given_name', - field=models.CharField(blank=True, max_length=30, null=True), - ), - migrations.AddField( - model_name='user', - name='google_id', - field=models.CharField(blank=True, max_length=50, null=True, unique=True), - ), - ] From 6aecf30cb03cf75ccb2a95ad7d05acd5f6413fda Mon Sep 17 00:00:00 2001 From: Cassidy Xu <68167806+cassidyxu@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:47:49 -0500 Subject: [PATCH 271/305] Delete src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py --- ...e_eateries_user_favorite_items_and_more.py | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py diff --git a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py b/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py deleted file mode 100644 index 47c42cb..0000000 --- a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 4.0 on 2024-10-09 17:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eatery', '0005_alter_eatery_campus_area'), - ('item', '0002_alter_item_id'), - ('user', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='favorite_eateries', - field=models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery'), - ), - migrations.AddField( - model_name='user', - name='favorite_items', - field=models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item'), - ), - migrations.AddField( - model_name='user', - name='is_admin', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='user', - name='name', - field=models.CharField(default='User', max_length=40), - ), - ] From 64a8e6857b879975ebc0b42e14cc8fccdb332585 Mon Sep 17 00:00:00 2001 From: Cassidy Xu <68167806+cassidyxu@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:47:49 -0500 Subject: [PATCH 272/305] Delete src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py --- ...e_eateries_user_favorite_items_and_more.py | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py diff --git a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py b/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py deleted file mode 100644 index 47c42cb..0000000 --- a/src/user/migrations/0002_user_favorite_eateries_user_favorite_items_and_more.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 4.0 on 2024-10-09 17:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('eatery', '0005_alter_eatery_campus_area'), - ('item', '0002_alter_item_id'), - ('user', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='user', - name='favorite_eateries', - field=models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery'), - ), - migrations.AddField( - model_name='user', - name='favorite_items', - field=models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item'), - ), - migrations.AddField( - model_name='user', - name='is_admin', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='user', - name='name', - field=models.CharField(default='User', max_length=40), - ), - ] From 5550cc2b1dd3eea8da8b933ee9140136cfca77ac Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Sun, 10 Nov 2024 13:13:23 -0500 Subject: [PATCH 273/305] Change favorite_items field to store item names as strings instead of IDs --- src/user/migrations/0001_initial.py | 12 ++- src/user/models.py | 10 +- src/user/serializers.py | 15 ++- src/user/views.py | 114 +++++++++++++------- src/user_session/migrations/0001_initial.py | 2 +- 5 files changed, 105 insertions(+), 48 deletions(-) diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py index 2abef68..2f8ef51 100644 --- a/src/user/migrations/0001_initial.py +++ b/src/user/migrations/0001_initial.py @@ -1,5 +1,6 @@ -# Generated by Django 4.0 on 2024-11-04 17:02 +# Generated by Django 4.0 on 2024-11-10 18:03 +import django.contrib.postgres.fields from django.db import migrations, models @@ -8,7 +9,6 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('item', '0002_alter_item_id'), ('eatery', '0005_alter_eatery_campus_area'), ] @@ -18,10 +18,12 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('netid', models.CharField(blank=True, max_length=10, null=True)), - ('name', models.CharField(default='User', max_length=40)), - ('is_admin', models.BooleanField(default=False)), + ('given_name', models.CharField(blank=True, max_length=30, null=True)), + ('family_name', models.CharField(blank=True, max_length=30, null=True)), + ('google_id', models.CharField(blank=True, max_length=50, null=True, unique=True)), + ('email', models.EmailField(blank=True, max_length=255, null=True)), + ('favorite_items', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), blank=True, default=list, size=None)), ('favorite_eateries', models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery')), - ('favorite_items', models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item')), ], ), ] diff --git a/src/user/models.py b/src/user/models.py index 615a5bb..4a3d940 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.contrib.postgres.fields import ArrayField class User(models.Model): @@ -7,13 +8,12 @@ class User(models.Model): family_name = models.CharField(max_length=30, null=True, blank=True) google_id = models.CharField(max_length=50, unique=True, null=True, blank=True) email = models.EmailField(max_length=255, null=True, blank=True) - favorite_items = models.ManyToManyField( - "item.Item", related_name="favorited_by", blank=True + favorite_items = ArrayField( + models.CharField(max_length=100), blank=True, default=list ) favorite_eateries = models.ManyToManyField( "eatery.Eatery", related_name="favorited_by", blank=True ) - + def __str__(self): - return f'{self.netid}' - \ No newline at end of file + return f"{self.netid}" diff --git a/src/user/serializers.py b/src/user/serializers.py index 45447d7..851cb05 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -3,6 +3,19 @@ class UserSerializer(serializers.ModelSerializer): + + favorite_items = serializers.ListField( + child=serializers.CharField(max_length=100), required=False + ) + class Meta: model = User - fields = ['id', 'given_name', 'family_name', 'netid', 'google_id', 'favorite_eateries', 'favorite_items'] + fields = [ + "id", + "given_name", + "family_name", + "netid", + "google_id", + "favorite_eateries", + "favorite_items", + ] diff --git a/src/user/views.py b/src/user/views.py index 8fd9850..330e72f 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -15,8 +15,6 @@ from user_session.models import UserSession import secrets import os - - class UserViewSet(viewsets.ModelViewSet): @@ -43,79 +41,123 @@ def remove_favorite_eatery(self, request, pk=None): user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) + # @action(detail=True, methods=["post"], url_path="item/add") + # def add_favorite_item(self, request, pk=None): + # user = get_object_or_404(User, pk=pk) + # item_id = request.data.get("item_id") + # item = get_object_or_404(Item, id=item_id) + # user.favorite_items.add(item) + # user.save() + # user_data = UserSerializer(user).data + # return Response(user_data, status=status.HTTP_200_OK) + + # @action(detail=True, methods=["post"], url_path="item/remove") + # def remove_favorite_item(self, request, pk=None): + # user = get_object_or_404(User, pk=pk) + # item_id = request.data.get("item_id") + # item = get_object_or_404(Item, id=item_id) + # user.favorite_items.remove(item) + # user.save() + # user_data = UserSerializer(user).data + # return Response(user_data, status=status.HTTP_200_OK) + @action(detail=True, methods=["post"], url_path="item/add") def add_favorite_item(self, request, pk=None): user = get_object_or_404(User, pk=pk) - item_id = request.data.get("item_id") - item = get_object_or_404(Item, id=item_id) - user.favorite_items.add(item) - user.save() + item_name = request.data.get("item_name") + + if item_name and item_name not in user.favorite_items: + user.favorite_items.append(item_name) + user.save() + user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) @action(detail=True, methods=["post"], url_path="item/remove") def remove_favorite_item(self, request, pk=None): user = get_object_or_404(User, pk=pk) - item_id = request.data.get("item_id") - item = get_object_or_404(Item, id=item_id) - user.favorite_items.remove(item) - user.save() + item_name = request.data.get("item_name") + + if item_name in user.favorite_items: + user.favorite_items.remove(item_name) + user.save() + user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) - - @action(detail=False, methods=['post'], url_path='login') + + @action(detail=False, methods=["post"], url_path="login") def login(self, request): - id_token_str = request.data.get('idToken') - user = request.data.get('user') + id_token_str = request.data.get("idToken") + user = request.data.get("user") if not id_token_str or not user: - return Response({'error': 'Invalid request'}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST + ) try: # verifies token - idinfo = id_token.verify_oauth2_token(id_token_str, requests.Request(), os.getenv('GOOGLE_CLIENT_ID')) + idinfo = id_token.verify_oauth2_token( + id_token_str, requests.Request(), os.getenv("GOOGLE_CLIENT_ID") + ) # ensures token is from Google - if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: - return Response({'error': 'Invalid token issuer'}, status=status.HTTP_401_UNAUTHORIZED) - - userid = idinfo['sub'] - email = idinfo['email'] + if idinfo["iss"] not in [ + "accounts.google.com", + "https://accounts.google.com", + ]: + return Response( + {"error": "Invalid token issuer"}, + status=status.HTTP_401_UNAUTHORIZED, + ) + + userid = idinfo["sub"] + email = idinfo["email"] # ensures email is Cornell email - if not email.endswith('@cornell.edu'): - return Response({'error': 'Non-Cornell email used'}, status=status.HTTP_401_UNAUTHORIZED) + if not email.endswith("@cornell.edu"): + return Response( + {"error": "Non-Cornell email used"}, + status=status.HTTP_401_UNAUTHORIZED, + ) # creates user if not exists user = User.objects.get_or_create( google_id=userid, defaults={ - 'email': email, - 'given_name': idinfo.get('given_name', ''), - 'family_name': idinfo.get('family_name', ''), - 'netid': email.split('@')[0], - } + "email": email, + "given_name": idinfo.get("given_name", ""), + "family_name": idinfo.get("family_name", ""), + "netid": email.split("@")[0], + }, ) # creates session token and makes new user session session_token = secrets.token_hex(20) UserSession.objects.create(user=user) - return Response({'session_token': session_token}, status=status.HTTP_200_OK) + return Response({"session_token": session_token}, status=status.HTTP_200_OK) except ValueError: - return Response({'error': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST) - + return Response( + {"error": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST + ) - @action(detail=False, methods=['post'], url_path='logout') + @action(detail=False, methods=["post"], url_path="logout") def logout(self, request): - session_token = request.data.get('session_token') + session_token = request.data.get("session_token") if not session_token: - return Response({'error': 'Session token required'}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Session token required"}, status=status.HTTP_400_BAD_REQUEST + ) try: # gets and deletes user session user_session = UserSession.objects.get(session_token=session_token) user_session.delete() - return Response({'message': 'Logged out successfully'}, status=status.HTTP_200_OK) + return Response( + {"message": "Logged out successfully"}, status=status.HTTP_200_OK + ) except UserSession.DoesNotExist: - return Response({'error': 'Invalid session token'}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Invalid session token"}, status=status.HTTP_400_BAD_REQUEST + ) diff --git a/src/user_session/migrations/0001_initial.py b/src/user_session/migrations/0001_initial.py index 6ba12b2..1cd80ab 100644 --- a/src/user_session/migrations/0001_initial.py +++ b/src/user_session/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2024-11-04 17:03 +# Generated by Django 4.0 on 2024-11-10 18:04 from django.db import migrations, models import django.db.models.deletion From 033f9c3f9aa07d70c857ef87b371c5769968b2f0 Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Sun, 10 Nov 2024 13:13:23 -0500 Subject: [PATCH 274/305] Change favorite_items field to store item names as strings instead of IDs --- src/user/migrations/0001_initial.py | 12 ++- src/user/models.py | 10 +- src/user/serializers.py | 15 ++- src/user/views.py | 114 +++++++++++++------- src/user_session/migrations/0001_initial.py | 2 +- 5 files changed, 105 insertions(+), 48 deletions(-) diff --git a/src/user/migrations/0001_initial.py b/src/user/migrations/0001_initial.py index 2abef68..2f8ef51 100644 --- a/src/user/migrations/0001_initial.py +++ b/src/user/migrations/0001_initial.py @@ -1,5 +1,6 @@ -# Generated by Django 4.0 on 2024-11-04 17:02 +# Generated by Django 4.0 on 2024-11-10 18:03 +import django.contrib.postgres.fields from django.db import migrations, models @@ -8,7 +9,6 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('item', '0002_alter_item_id'), ('eatery', '0005_alter_eatery_campus_area'), ] @@ -18,10 +18,12 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('netid', models.CharField(blank=True, max_length=10, null=True)), - ('name', models.CharField(default='User', max_length=40)), - ('is_admin', models.BooleanField(default=False)), + ('given_name', models.CharField(blank=True, max_length=30, null=True)), + ('family_name', models.CharField(blank=True, max_length=30, null=True)), + ('google_id', models.CharField(blank=True, max_length=50, null=True, unique=True)), + ('email', models.EmailField(blank=True, max_length=255, null=True)), + ('favorite_items', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), blank=True, default=list, size=None)), ('favorite_eateries', models.ManyToManyField(blank=True, related_name='favorited_by', to='eatery.Eatery')), - ('favorite_items', models.ManyToManyField(blank=True, related_name='favorited_by', to='item.Item')), ], ), ] diff --git a/src/user/models.py b/src/user/models.py index 615a5bb..4a3d940 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.contrib.postgres.fields import ArrayField class User(models.Model): @@ -7,13 +8,12 @@ class User(models.Model): family_name = models.CharField(max_length=30, null=True, blank=True) google_id = models.CharField(max_length=50, unique=True, null=True, blank=True) email = models.EmailField(max_length=255, null=True, blank=True) - favorite_items = models.ManyToManyField( - "item.Item", related_name="favorited_by", blank=True + favorite_items = ArrayField( + models.CharField(max_length=100), blank=True, default=list ) favorite_eateries = models.ManyToManyField( "eatery.Eatery", related_name="favorited_by", blank=True ) - + def __str__(self): - return f'{self.netid}' - \ No newline at end of file + return f"{self.netid}" diff --git a/src/user/serializers.py b/src/user/serializers.py index 45447d7..851cb05 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -3,6 +3,19 @@ class UserSerializer(serializers.ModelSerializer): + + favorite_items = serializers.ListField( + child=serializers.CharField(max_length=100), required=False + ) + class Meta: model = User - fields = ['id', 'given_name', 'family_name', 'netid', 'google_id', 'favorite_eateries', 'favorite_items'] + fields = [ + "id", + "given_name", + "family_name", + "netid", + "google_id", + "favorite_eateries", + "favorite_items", + ] diff --git a/src/user/views.py b/src/user/views.py index 8fd9850..330e72f 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -15,8 +15,6 @@ from user_session.models import UserSession import secrets import os - - class UserViewSet(viewsets.ModelViewSet): @@ -43,79 +41,123 @@ def remove_favorite_eatery(self, request, pk=None): user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) + # @action(detail=True, methods=["post"], url_path="item/add") + # def add_favorite_item(self, request, pk=None): + # user = get_object_or_404(User, pk=pk) + # item_id = request.data.get("item_id") + # item = get_object_or_404(Item, id=item_id) + # user.favorite_items.add(item) + # user.save() + # user_data = UserSerializer(user).data + # return Response(user_data, status=status.HTTP_200_OK) + + # @action(detail=True, methods=["post"], url_path="item/remove") + # def remove_favorite_item(self, request, pk=None): + # user = get_object_or_404(User, pk=pk) + # item_id = request.data.get("item_id") + # item = get_object_or_404(Item, id=item_id) + # user.favorite_items.remove(item) + # user.save() + # user_data = UserSerializer(user).data + # return Response(user_data, status=status.HTTP_200_OK) + @action(detail=True, methods=["post"], url_path="item/add") def add_favorite_item(self, request, pk=None): user = get_object_or_404(User, pk=pk) - item_id = request.data.get("item_id") - item = get_object_or_404(Item, id=item_id) - user.favorite_items.add(item) - user.save() + item_name = request.data.get("item_name") + + if item_name and item_name not in user.favorite_items: + user.favorite_items.append(item_name) + user.save() + user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) @action(detail=True, methods=["post"], url_path="item/remove") def remove_favorite_item(self, request, pk=None): user = get_object_or_404(User, pk=pk) - item_id = request.data.get("item_id") - item = get_object_or_404(Item, id=item_id) - user.favorite_items.remove(item) - user.save() + item_name = request.data.get("item_name") + + if item_name in user.favorite_items: + user.favorite_items.remove(item_name) + user.save() + user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) - - @action(detail=False, methods=['post'], url_path='login') + + @action(detail=False, methods=["post"], url_path="login") def login(self, request): - id_token_str = request.data.get('idToken') - user = request.data.get('user') + id_token_str = request.data.get("idToken") + user = request.data.get("user") if not id_token_str or not user: - return Response({'error': 'Invalid request'}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST + ) try: # verifies token - idinfo = id_token.verify_oauth2_token(id_token_str, requests.Request(), os.getenv('GOOGLE_CLIENT_ID')) + idinfo = id_token.verify_oauth2_token( + id_token_str, requests.Request(), os.getenv("GOOGLE_CLIENT_ID") + ) # ensures token is from Google - if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: - return Response({'error': 'Invalid token issuer'}, status=status.HTTP_401_UNAUTHORIZED) - - userid = idinfo['sub'] - email = idinfo['email'] + if idinfo["iss"] not in [ + "accounts.google.com", + "https://accounts.google.com", + ]: + return Response( + {"error": "Invalid token issuer"}, + status=status.HTTP_401_UNAUTHORIZED, + ) + + userid = idinfo["sub"] + email = idinfo["email"] # ensures email is Cornell email - if not email.endswith('@cornell.edu'): - return Response({'error': 'Non-Cornell email used'}, status=status.HTTP_401_UNAUTHORIZED) + if not email.endswith("@cornell.edu"): + return Response( + {"error": "Non-Cornell email used"}, + status=status.HTTP_401_UNAUTHORIZED, + ) # creates user if not exists user = User.objects.get_or_create( google_id=userid, defaults={ - 'email': email, - 'given_name': idinfo.get('given_name', ''), - 'family_name': idinfo.get('family_name', ''), - 'netid': email.split('@')[0], - } + "email": email, + "given_name": idinfo.get("given_name", ""), + "family_name": idinfo.get("family_name", ""), + "netid": email.split("@")[0], + }, ) # creates session token and makes new user session session_token = secrets.token_hex(20) UserSession.objects.create(user=user) - return Response({'session_token': session_token}, status=status.HTTP_200_OK) + return Response({"session_token": session_token}, status=status.HTTP_200_OK) except ValueError: - return Response({'error': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST) - + return Response( + {"error": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST + ) - @action(detail=False, methods=['post'], url_path='logout') + @action(detail=False, methods=["post"], url_path="logout") def logout(self, request): - session_token = request.data.get('session_token') + session_token = request.data.get("session_token") if not session_token: - return Response({'error': 'Session token required'}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Session token required"}, status=status.HTTP_400_BAD_REQUEST + ) try: # gets and deletes user session user_session = UserSession.objects.get(session_token=session_token) user_session.delete() - return Response({'message': 'Logged out successfully'}, status=status.HTTP_200_OK) + return Response( + {"message": "Logged out successfully"}, status=status.HTTP_200_OK + ) except UserSession.DoesNotExist: - return Response({'error': 'Invalid session token'}, status=status.HTTP_400_BAD_REQUEST) + return Response( + {"error": "Invalid session token"}, status=status.HTTP_400_BAD_REQUEST + ) diff --git a/src/user_session/migrations/0001_initial.py b/src/user_session/migrations/0001_initial.py index 6ba12b2..1cd80ab 100644 --- a/src/user_session/migrations/0001_initial.py +++ b/src/user_session/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.0 on 2024-11-04 17:03 +# Generated by Django 4.0 on 2024-11-10 18:04 from django.db import migrations, models import django.db.models.deletion From 06642f67c124e5a3b3eeefc38721fa3f170b499a Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Sun, 10 Nov 2024 13:13:32 -0500 Subject: [PATCH 275/305] remove comments --- src/user/views.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/user/views.py b/src/user/views.py index 330e72f..156da43 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -41,26 +41,6 @@ def remove_favorite_eatery(self, request, pk=None): user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) - # @action(detail=True, methods=["post"], url_path="item/add") - # def add_favorite_item(self, request, pk=None): - # user = get_object_or_404(User, pk=pk) - # item_id = request.data.get("item_id") - # item = get_object_or_404(Item, id=item_id) - # user.favorite_items.add(item) - # user.save() - # user_data = UserSerializer(user).data - # return Response(user_data, status=status.HTTP_200_OK) - - # @action(detail=True, methods=["post"], url_path="item/remove") - # def remove_favorite_item(self, request, pk=None): - # user = get_object_or_404(User, pk=pk) - # item_id = request.data.get("item_id") - # item = get_object_or_404(Item, id=item_id) - # user.favorite_items.remove(item) - # user.save() - # user_data = UserSerializer(user).data - # return Response(user_data, status=status.HTTP_200_OK) - @action(detail=True, methods=["post"], url_path="item/add") def add_favorite_item(self, request, pk=None): user = get_object_or_404(User, pk=pk) From 73552d9452f4b6bd3add5c628e499d254fa82cca Mon Sep 17 00:00:00 2001 From: Cassidy Xu Date: Sun, 10 Nov 2024 13:13:32 -0500 Subject: [PATCH 276/305] remove comments --- src/user/views.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/user/views.py b/src/user/views.py index 330e72f..156da43 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -41,26 +41,6 @@ def remove_favorite_eatery(self, request, pk=None): user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) - # @action(detail=True, methods=["post"], url_path="item/add") - # def add_favorite_item(self, request, pk=None): - # user = get_object_or_404(User, pk=pk) - # item_id = request.data.get("item_id") - # item = get_object_or_404(Item, id=item_id) - # user.favorite_items.add(item) - # user.save() - # user_data = UserSerializer(user).data - # return Response(user_data, status=status.HTTP_200_OK) - - # @action(detail=True, methods=["post"], url_path="item/remove") - # def remove_favorite_item(self, request, pk=None): - # user = get_object_or_404(User, pk=pk) - # item_id = request.data.get("item_id") - # item = get_object_or_404(Item, id=item_id) - # user.favorite_items.remove(item) - # user.save() - # user_data = UserSerializer(user).data - # return Response(user_data, status=status.HTTP_200_OK) - @action(detail=True, methods=["post"], url_path="item/add") def add_favorite_item(self, request, pk=None): user = get_object_or_404(User, pk=pk) From 9d9de8a0268af0d6565d5eb568230e79a53c5bf2 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 19 Nov 2024 16:02:10 -0500 Subject: [PATCH 277/305] Address unnecessary imports and session token unique check --- src/user/urls.py | 1 - src/user/views.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/user/urls.py b/src/user/urls.py index 2201a52..dbf70f2 100644 --- a/src/user/urls.py +++ b/src/user/urls.py @@ -1,5 +1,4 @@ from django.urls import path, include -from django.views.decorators.http import require_POST from user.views import UserViewSet from rest_framework.routers import DefaultRouter diff --git a/src/user/views.py b/src/user/views.py index 156da43..436dbdc 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -4,11 +4,8 @@ from user.models import User from user.serializers import UserSerializer from eatery.models import Eatery -from item.models import Item from django.shortcuts import get_object_or_404 -from rest_framework.views import APIView from rest_framework.response import Response -from django.conf import settings from google.oauth2 import id_token from google.auth.transport import requests from user.models import User @@ -114,6 +111,8 @@ def login(self, request): # creates session token and makes new user session session_token = secrets.token_hex(20) + while UserSession.objects.filter(session_token=session_token).exists(): + session_token = secrets.token_hex(20) UserSession.objects.create(user=user) return Response({"session_token": session_token}, status=status.HTTP_200_OK) From 181ed205f15c0a59ce48fe1f5438fa7c534a11bf Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 19 Nov 2024 16:02:10 -0500 Subject: [PATCH 278/305] Address unnecessary imports and session token unique check --- src/user/urls.py | 1 - src/user/views.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/user/urls.py b/src/user/urls.py index 2201a52..dbf70f2 100644 --- a/src/user/urls.py +++ b/src/user/urls.py @@ -1,5 +1,4 @@ from django.urls import path, include -from django.views.decorators.http import require_POST from user.views import UserViewSet from rest_framework.routers import DefaultRouter diff --git a/src/user/views.py b/src/user/views.py index 156da43..436dbdc 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -4,11 +4,8 @@ from user.models import User from user.serializers import UserSerializer from eatery.models import Eatery -from item.models import Item from django.shortcuts import get_object_or_404 -from rest_framework.views import APIView from rest_framework.response import Response -from django.conf import settings from google.oauth2 import id_token from google.auth.transport import requests from user.models import User @@ -114,6 +111,8 @@ def login(self, request): # creates session token and makes new user session session_token = secrets.token_hex(20) + while UserSession.objects.filter(session_token=session_token).exists(): + session_token = secrets.token_hex(20) UserSession.objects.create(user=user) return Response({"session_token": session_token}, status=status.HTTP_200_OK) From 896c5ec949c998fd55092ab9789373139f50e1c7 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 19 Nov 2024 17:44:34 -0500 Subject: [PATCH 279/305] add new session management & login/logout --- .../__init__.py | 0 src/{user_session => device_token}/admin.py | 0 src/{user_session => device_token}/apps.py | 4 +- .../migrations/0001_initial.py | 11 +- .../migrations/__init__.py | 0 src/device_token/models.py | 12 ++ src/device_token/serializers.py | 7 + src/{user_session => device_token}/tests.py | 0 src/{user_session => device_token}/urls.py | 4 +- src/device_token/views.py | 8 + src/eatery_blue_backend/settings.py | 2 +- src/eatery_blue_backend/urls.py | 2 +- src/user/views.py | 156 ++++++++++++------ src/user_session/models.py | 13 -- src/user_session/serializers.py | 7 - src/user_session/views.py | 8 - 16 files changed, 142 insertions(+), 92 deletions(-) rename src/{user_session => device_token}/__init__.py (100%) rename src/{user_session => device_token}/admin.py (100%) rename src/{user_session => device_token}/apps.py (59%) rename src/{user_session => device_token}/migrations/0001_initial.py (60%) rename src/{user_session => device_token}/migrations/__init__.py (100%) create mode 100644 src/device_token/models.py create mode 100644 src/device_token/serializers.py rename src/{user_session => device_token}/tests.py (100%) rename src/{user_session => device_token}/urls.py (65%) create mode 100644 src/device_token/views.py delete mode 100644 src/user_session/models.py delete mode 100644 src/user_session/serializers.py delete mode 100644 src/user_session/views.py diff --git a/src/user_session/__init__.py b/src/device_token/__init__.py similarity index 100% rename from src/user_session/__init__.py rename to src/device_token/__init__.py diff --git a/src/user_session/admin.py b/src/device_token/admin.py similarity index 100% rename from src/user_session/admin.py rename to src/device_token/admin.py diff --git a/src/user_session/apps.py b/src/device_token/apps.py similarity index 59% rename from src/user_session/apps.py rename to src/device_token/apps.py index 6756df9..1b9b57f 100644 --- a/src/user_session/apps.py +++ b/src/device_token/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class UserSessionConfig(AppConfig): +class DeviceTokenConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'user_session' + name = 'device_token' diff --git a/src/user_session/migrations/0001_initial.py b/src/device_token/migrations/0001_initial.py similarity index 60% rename from src/user_session/migrations/0001_initial.py rename to src/device_token/migrations/0001_initial.py index 1cd80ab..63959a5 100644 --- a/src/user_session/migrations/0001_initial.py +++ b/src/device_token/migrations/0001_initial.py @@ -1,5 +1,3 @@ -# Generated by Django 4.0 on 2024-11-10 18:04 - from django.db import migrations, models import django.db.models.deletion @@ -14,12 +12,11 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='UserSession', + name='DeviceToken', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), - ('session_token', models.CharField(max_length=40)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_sessions', to='user.user')), + ('device_token', models.CharField(max_length=40)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_tokens', to='user.user')), ], ), - ] + ] \ No newline at end of file diff --git a/src/user_session/migrations/__init__.py b/src/device_token/migrations/__init__.py similarity index 100% rename from src/user_session/migrations/__init__.py rename to src/device_token/migrations/__init__.py diff --git a/src/device_token/models.py b/src/device_token/models.py new file mode 100644 index 0000000..c81c14d --- /dev/null +++ b/src/device_token/models.py @@ -0,0 +1,12 @@ +from django.db import models +from user.models import User + +class DeviceToken(models.Model): + id = models.AutoField(primary_key=True) + user = models.ForeignKey( + User, related_name="device_tokens", on_delete=models.CASCADE + ) + device_token = models.CharField(max_length=40) + + def __str__(self): + return f'{self.user.netid} - {self.device_token}' \ No newline at end of file diff --git a/src/device_token/serializers.py b/src/device_token/serializers.py new file mode 100644 index 0000000..dec8387 --- /dev/null +++ b/src/device_token/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from device_token.models import DeviceToken + +class DeviceTokenSerializer(serializers.ModelSerializer): + class Meta: + model = DeviceToken + fields = ['id', 'user', 'device_token'] \ No newline at end of file diff --git a/src/user_session/tests.py b/src/device_token/tests.py similarity index 100% rename from src/user_session/tests.py rename to src/device_token/tests.py diff --git a/src/user_session/urls.py b/src/device_token/urls.py similarity index 65% rename from src/user_session/urls.py rename to src/device_token/urls.py index baa6cc0..3c810a7 100644 --- a/src/user_session/urls.py +++ b/src/device_token/urls.py @@ -1,10 +1,10 @@ from django.urls import path, include -from user_session.views import UserSessionViewSet +from device_token.views import DeviceTokenViewSet from rest_framework.routers import DefaultRouter router = DefaultRouter() -router.register("", UserSessionViewSet) +router.register("", DeviceTokenViewSet) urlpatterns = [ path("", include(router.urls)), diff --git a/src/device_token/views.py b/src/device_token/views.py new file mode 100644 index 0000000..2e6bf1d --- /dev/null +++ b/src/device_token/views.py @@ -0,0 +1,8 @@ +from rest_framework import viewsets +from device_token.models import DeviceToken +from device_token.serializers import DeviceTokenSerializer + + +class DeviceTokenViewSet(viewsets.ModelViewSet): + queryset = DeviceToken.objects.all() + serializer_class = DeviceTokenSerializer diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 819c223..27a1841 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -61,7 +61,7 @@ "item", "category", "user", - "user_session", + "device_token", # Third party "rest_framework", ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 0ea46a4..b1de75f 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -22,6 +22,6 @@ path("category/", include("category.urls")), path("report/", include("report.urls")), path("user/", include("user.urls")), - path("user-session/", include("user_session.urls")), + path("device-token/", include("device_token.urls")), path("docs/", schema_view.with_ui("swagger", cache_timeout=0)), ] diff --git a/src/user/views.py b/src/user/views.py index 436dbdc..34a84ae 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -9,8 +9,7 @@ from google.oauth2 import id_token from google.auth.transport import requests from user.models import User -from user_session.models import UserSession -import secrets +from device_token.models import DeviceToken import os @@ -64,79 +63,134 @@ def remove_favorite_item(self, request, pk=None): @action(detail=False, methods=["post"], url_path="login") def login(self, request): + device_token = request.data.get("device_token") id_token_str = request.data.get("idToken") - user = request.data.get("user") - if not id_token_str or not user: + if not device_token: return Response( - {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST + {"error": "device_token is required"}, + status=status.HTTP_400_BAD_REQUEST, ) try: - # verifies token - idinfo = id_token.verify_oauth2_token( - id_token_str, requests.Request(), os.getenv("GOOGLE_CLIENT_ID") + # create device token if doesnt exist + device_token_obj = DeviceToken.objects.get_or_create( + device_token=device_token, + defaults={'user': User.objects.create(netid=None)} + ) + user = device_token_obj.user + except Exception as e: + return Response( + {"error": f"Error processing device token: {str(e)}"}, + status=status.HTTP_400_BAD_REQUEST, ) - # ensures token is from Google - if idinfo["iss"] not in [ - "accounts.google.com", - "https://accounts.google.com", - ]: - return Response( - {"error": "Invalid token issuer"}, - status=status.HTTP_401_UNAUTHORIZED, + if id_token_str: + try: + # verifies token + idinfo = id_token.verify_oauth2_token( + id_token_str, requests.Request(), os.getenv("GOOGLE_CLIENT_ID") ) - userid = idinfo["sub"] - email = idinfo["email"] + # ensures token is from Google + if idinfo["iss"] not in [ + "accounts.google.com", + "https://accounts.google.com", + ]: + return Response( + {"error": "Invalid token issuer"}, + status=status.HTTP_401_UNAUTHORIZED, + ) + + userid = idinfo["sub"] + email = idinfo["email"] + + # ensures email is Cornell email + if not email.endswith("@cornell.edu"): + return Response( + {"error": "Non-Cornell email used"}, + status=status.HTTP_401_UNAUTHORIZED, + ) + + # creates user if not exists + authenticated_user = User.objects.get_or_create( + google_id=userid, + defaults={ + "email": email, + "given_name": idinfo.get("given_name", ""), + "family_name": idinfo.get("family_name", ""), + "netid": email.split("@")[0], + }, + ) - # ensures email is Cornell email - if not email.endswith("@cornell.edu"): + if user == authenticated_user: + pass + elif user.netid is None: + # was unauthorized user, update to authenticated user + + # copy favorites + authenticated_user.favorite_items = list( + set(authenticated_user.favorite_items + user.favorite_items) + ) + authenticated_user.favorite_eateries.add(*user.favorite_eateries.all()) + authenticated_user.save() + + # copy device token + device_token_obj.user = authenticated_user + device_token_obj.save() + + # delete unauthorized user after merging + user.delete() + else: + # device token is associated with a different user, should not happen + return Response( + {"error": "Device token is associated with another user"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + return Response({"device_token": device_token}, status=status.HTTP_200_OK) + + except ValueError: return Response( - {"error": "Non-Cornell email used"}, - status=status.HTTP_401_UNAUTHORIZED, + {"error": "Invalid idToken"}, + status=status.HTTP_400_BAD_REQUEST, + ) + else: + # no idToken provided, user is anonymous + if user.netid is None: + pass + else: + # device token associated with an authenticated user but no idToken provided + return Response( + {"error": "Device token associated with an authenticated user"}, + status=status.HTTP_400_BAD_REQUEST, ) - # creates user if not exists - user = User.objects.get_or_create( - google_id=userid, - defaults={ - "email": email, - "given_name": idinfo.get("given_name", ""), - "family_name": idinfo.get("family_name", ""), - "netid": email.split("@")[0], - }, - ) - - # creates session token and makes new user session - session_token = secrets.token_hex(20) - while UserSession.objects.filter(session_token=session_token).exists(): - session_token = secrets.token_hex(20) - UserSession.objects.create(user=user) + return Response({"device_token": device_token}, status=status.HTTP_200_OK) - return Response({"session_token": session_token}, status=status.HTTP_200_OK) - except ValueError: - return Response( - {"error": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST - ) @action(detail=False, methods=["post"], url_path="logout") def logout(self, request): - session_token = request.data.get("session_token") - if not session_token: + device_token = request.data.get("device_token") + if not device_token: return Response( - {"error": "Session token required"}, status=status.HTTP_400_BAD_REQUEST + {"error": "Device token required"}, status=status.HTTP_400_BAD_REQUEST ) try: - # gets and deletes user session - user_session = UserSession.objects.get(session_token=session_token) - user_session.delete() + # delete the device token + device_token_obj = DeviceToken.objects.get(device_token=device_token) + user = device_token_obj.user + device_token_obj.delete() + + # if user was anonymous and has no device tokens, delete the user + if user.netid is None and not DeviceToken.objects.filter(user=user).exists(): + user.delete() + return Response( {"message": "Logged out successfully"}, status=status.HTTP_200_OK ) - except UserSession.DoesNotExist: + except DeviceToken.DoesNotExist: return Response( - {"error": "Invalid session token"}, status=status.HTTP_400_BAD_REQUEST + {"error": "Invalid device token"}, status=status.HTTP_400_BAD_REQUEST ) diff --git a/src/user_session/models.py b/src/user_session/models.py deleted file mode 100644 index bcb664d..0000000 --- a/src/user_session/models.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.db import models -from user.models import User - -class UserSession(models.Model): - id = models.AutoField(primary_key=True) - user = models.ForeignKey( - User, related_name="user_sessions", on_delete=models.CASCADE - ) - session_token = models.CharField(max_length=40) - created_at = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return f'{self.user.netid} - {self.session_token}' \ No newline at end of file diff --git a/src/user_session/serializers.py b/src/user_session/serializers.py deleted file mode 100644 index 7b232da..0000000 --- a/src/user_session/serializers.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import serializers -from user_session.models import UserSession - -class UserSessionSerializer(serializers.ModelSerializer): - class Meta: - model = UserSession - fields = ['id', 'user', 'session_token', 'created_at'] \ No newline at end of file diff --git a/src/user_session/views.py b/src/user_session/views.py deleted file mode 100644 index d0b271a..0000000 --- a/src/user_session/views.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import viewsets -from user_session.models import UserSession -from user_session.serializers import UserSessionSerializer - - -class UserSessionViewSet(viewsets.ModelViewSet): - queryset = UserSession.objects.all() - serializer_class = UserSessionSerializer From 26dc530bc1ee3a0073db34940164ec31b30405d7 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 19 Nov 2024 17:44:34 -0500 Subject: [PATCH 280/305] add new session management & login/logout --- .../__init__.py | 0 src/{user_session => device_token}/admin.py | 0 src/{user_session => device_token}/apps.py | 4 +- .../migrations/0001_initial.py | 11 +- .../migrations/__init__.py | 0 src/device_token/models.py | 12 ++ src/device_token/serializers.py | 7 + src/{user_session => device_token}/tests.py | 0 src/{user_session => device_token}/urls.py | 4 +- src/device_token/views.py | 8 + src/eatery_blue_backend/settings.py | 2 +- src/eatery_blue_backend/urls.py | 2 +- src/user/views.py | 156 ++++++++++++------ src/user_session/models.py | 13 -- src/user_session/serializers.py | 7 - src/user_session/views.py | 8 - 16 files changed, 142 insertions(+), 92 deletions(-) rename src/{user_session => device_token}/__init__.py (100%) rename src/{user_session => device_token}/admin.py (100%) rename src/{user_session => device_token}/apps.py (59%) rename src/{user_session => device_token}/migrations/0001_initial.py (60%) rename src/{user_session => device_token}/migrations/__init__.py (100%) create mode 100644 src/device_token/models.py create mode 100644 src/device_token/serializers.py rename src/{user_session => device_token}/tests.py (100%) rename src/{user_session => device_token}/urls.py (65%) create mode 100644 src/device_token/views.py delete mode 100644 src/user_session/models.py delete mode 100644 src/user_session/serializers.py delete mode 100644 src/user_session/views.py diff --git a/src/user_session/__init__.py b/src/device_token/__init__.py similarity index 100% rename from src/user_session/__init__.py rename to src/device_token/__init__.py diff --git a/src/user_session/admin.py b/src/device_token/admin.py similarity index 100% rename from src/user_session/admin.py rename to src/device_token/admin.py diff --git a/src/user_session/apps.py b/src/device_token/apps.py similarity index 59% rename from src/user_session/apps.py rename to src/device_token/apps.py index 6756df9..1b9b57f 100644 --- a/src/user_session/apps.py +++ b/src/device_token/apps.py @@ -1,5 +1,5 @@ from django.apps import AppConfig -class UserSessionConfig(AppConfig): +class DeviceTokenConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'user_session' + name = 'device_token' diff --git a/src/user_session/migrations/0001_initial.py b/src/device_token/migrations/0001_initial.py similarity index 60% rename from src/user_session/migrations/0001_initial.py rename to src/device_token/migrations/0001_initial.py index 1cd80ab..63959a5 100644 --- a/src/user_session/migrations/0001_initial.py +++ b/src/device_token/migrations/0001_initial.py @@ -1,5 +1,3 @@ -# Generated by Django 4.0 on 2024-11-10 18:04 - from django.db import migrations, models import django.db.models.deletion @@ -14,12 +12,11 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='UserSession', + name='DeviceToken', fields=[ ('id', models.AutoField(primary_key=True, serialize=False)), - ('session_token', models.CharField(max_length=40)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_sessions', to='user.user')), + ('device_token', models.CharField(max_length=40)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_tokens', to='user.user')), ], ), - ] + ] \ No newline at end of file diff --git a/src/user_session/migrations/__init__.py b/src/device_token/migrations/__init__.py similarity index 100% rename from src/user_session/migrations/__init__.py rename to src/device_token/migrations/__init__.py diff --git a/src/device_token/models.py b/src/device_token/models.py new file mode 100644 index 0000000..c81c14d --- /dev/null +++ b/src/device_token/models.py @@ -0,0 +1,12 @@ +from django.db import models +from user.models import User + +class DeviceToken(models.Model): + id = models.AutoField(primary_key=True) + user = models.ForeignKey( + User, related_name="device_tokens", on_delete=models.CASCADE + ) + device_token = models.CharField(max_length=40) + + def __str__(self): + return f'{self.user.netid} - {self.device_token}' \ No newline at end of file diff --git a/src/device_token/serializers.py b/src/device_token/serializers.py new file mode 100644 index 0000000..dec8387 --- /dev/null +++ b/src/device_token/serializers.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from device_token.models import DeviceToken + +class DeviceTokenSerializer(serializers.ModelSerializer): + class Meta: + model = DeviceToken + fields = ['id', 'user', 'device_token'] \ No newline at end of file diff --git a/src/user_session/tests.py b/src/device_token/tests.py similarity index 100% rename from src/user_session/tests.py rename to src/device_token/tests.py diff --git a/src/user_session/urls.py b/src/device_token/urls.py similarity index 65% rename from src/user_session/urls.py rename to src/device_token/urls.py index baa6cc0..3c810a7 100644 --- a/src/user_session/urls.py +++ b/src/device_token/urls.py @@ -1,10 +1,10 @@ from django.urls import path, include -from user_session.views import UserSessionViewSet +from device_token.views import DeviceTokenViewSet from rest_framework.routers import DefaultRouter router = DefaultRouter() -router.register("", UserSessionViewSet) +router.register("", DeviceTokenViewSet) urlpatterns = [ path("", include(router.urls)), diff --git a/src/device_token/views.py b/src/device_token/views.py new file mode 100644 index 0000000..2e6bf1d --- /dev/null +++ b/src/device_token/views.py @@ -0,0 +1,8 @@ +from rest_framework import viewsets +from device_token.models import DeviceToken +from device_token.serializers import DeviceTokenSerializer + + +class DeviceTokenViewSet(viewsets.ModelViewSet): + queryset = DeviceToken.objects.all() + serializer_class = DeviceTokenSerializer diff --git a/src/eatery_blue_backend/settings.py b/src/eatery_blue_backend/settings.py index 819c223..27a1841 100644 --- a/src/eatery_blue_backend/settings.py +++ b/src/eatery_blue_backend/settings.py @@ -61,7 +61,7 @@ "item", "category", "user", - "user_session", + "device_token", # Third party "rest_framework", ] diff --git a/src/eatery_blue_backend/urls.py b/src/eatery_blue_backend/urls.py index 0ea46a4..b1de75f 100644 --- a/src/eatery_blue_backend/urls.py +++ b/src/eatery_blue_backend/urls.py @@ -22,6 +22,6 @@ path("category/", include("category.urls")), path("report/", include("report.urls")), path("user/", include("user.urls")), - path("user-session/", include("user_session.urls")), + path("device-token/", include("device_token.urls")), path("docs/", schema_view.with_ui("swagger", cache_timeout=0)), ] diff --git a/src/user/views.py b/src/user/views.py index 436dbdc..34a84ae 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -9,8 +9,7 @@ from google.oauth2 import id_token from google.auth.transport import requests from user.models import User -from user_session.models import UserSession -import secrets +from device_token.models import DeviceToken import os @@ -64,79 +63,134 @@ def remove_favorite_item(self, request, pk=None): @action(detail=False, methods=["post"], url_path="login") def login(self, request): + device_token = request.data.get("device_token") id_token_str = request.data.get("idToken") - user = request.data.get("user") - if not id_token_str or not user: + if not device_token: return Response( - {"error": "Invalid request"}, status=status.HTTP_400_BAD_REQUEST + {"error": "device_token is required"}, + status=status.HTTP_400_BAD_REQUEST, ) try: - # verifies token - idinfo = id_token.verify_oauth2_token( - id_token_str, requests.Request(), os.getenv("GOOGLE_CLIENT_ID") + # create device token if doesnt exist + device_token_obj = DeviceToken.objects.get_or_create( + device_token=device_token, + defaults={'user': User.objects.create(netid=None)} + ) + user = device_token_obj.user + except Exception as e: + return Response( + {"error": f"Error processing device token: {str(e)}"}, + status=status.HTTP_400_BAD_REQUEST, ) - # ensures token is from Google - if idinfo["iss"] not in [ - "accounts.google.com", - "https://accounts.google.com", - ]: - return Response( - {"error": "Invalid token issuer"}, - status=status.HTTP_401_UNAUTHORIZED, + if id_token_str: + try: + # verifies token + idinfo = id_token.verify_oauth2_token( + id_token_str, requests.Request(), os.getenv("GOOGLE_CLIENT_ID") ) - userid = idinfo["sub"] - email = idinfo["email"] + # ensures token is from Google + if idinfo["iss"] not in [ + "accounts.google.com", + "https://accounts.google.com", + ]: + return Response( + {"error": "Invalid token issuer"}, + status=status.HTTP_401_UNAUTHORIZED, + ) + + userid = idinfo["sub"] + email = idinfo["email"] + + # ensures email is Cornell email + if not email.endswith("@cornell.edu"): + return Response( + {"error": "Non-Cornell email used"}, + status=status.HTTP_401_UNAUTHORIZED, + ) + + # creates user if not exists + authenticated_user = User.objects.get_or_create( + google_id=userid, + defaults={ + "email": email, + "given_name": idinfo.get("given_name", ""), + "family_name": idinfo.get("family_name", ""), + "netid": email.split("@")[0], + }, + ) - # ensures email is Cornell email - if not email.endswith("@cornell.edu"): + if user == authenticated_user: + pass + elif user.netid is None: + # was unauthorized user, update to authenticated user + + # copy favorites + authenticated_user.favorite_items = list( + set(authenticated_user.favorite_items + user.favorite_items) + ) + authenticated_user.favorite_eateries.add(*user.favorite_eateries.all()) + authenticated_user.save() + + # copy device token + device_token_obj.user = authenticated_user + device_token_obj.save() + + # delete unauthorized user after merging + user.delete() + else: + # device token is associated with a different user, should not happen + return Response( + {"error": "Device token is associated with another user"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + return Response({"device_token": device_token}, status=status.HTTP_200_OK) + + except ValueError: return Response( - {"error": "Non-Cornell email used"}, - status=status.HTTP_401_UNAUTHORIZED, + {"error": "Invalid idToken"}, + status=status.HTTP_400_BAD_REQUEST, + ) + else: + # no idToken provided, user is anonymous + if user.netid is None: + pass + else: + # device token associated with an authenticated user but no idToken provided + return Response( + {"error": "Device token associated with an authenticated user"}, + status=status.HTTP_400_BAD_REQUEST, ) - # creates user if not exists - user = User.objects.get_or_create( - google_id=userid, - defaults={ - "email": email, - "given_name": idinfo.get("given_name", ""), - "family_name": idinfo.get("family_name", ""), - "netid": email.split("@")[0], - }, - ) - - # creates session token and makes new user session - session_token = secrets.token_hex(20) - while UserSession.objects.filter(session_token=session_token).exists(): - session_token = secrets.token_hex(20) - UserSession.objects.create(user=user) + return Response({"device_token": device_token}, status=status.HTTP_200_OK) - return Response({"session_token": session_token}, status=status.HTTP_200_OK) - except ValueError: - return Response( - {"error": "Invalid token"}, status=status.HTTP_400_BAD_REQUEST - ) @action(detail=False, methods=["post"], url_path="logout") def logout(self, request): - session_token = request.data.get("session_token") - if not session_token: + device_token = request.data.get("device_token") + if not device_token: return Response( - {"error": "Session token required"}, status=status.HTTP_400_BAD_REQUEST + {"error": "Device token required"}, status=status.HTTP_400_BAD_REQUEST ) try: - # gets and deletes user session - user_session = UserSession.objects.get(session_token=session_token) - user_session.delete() + # delete the device token + device_token_obj = DeviceToken.objects.get(device_token=device_token) + user = device_token_obj.user + device_token_obj.delete() + + # if user was anonymous and has no device tokens, delete the user + if user.netid is None and not DeviceToken.objects.filter(user=user).exists(): + user.delete() + return Response( {"message": "Logged out successfully"}, status=status.HTTP_200_OK ) - except UserSession.DoesNotExist: + except DeviceToken.DoesNotExist: return Response( - {"error": "Invalid session token"}, status=status.HTTP_400_BAD_REQUEST + {"error": "Invalid device token"}, status=status.HTTP_400_BAD_REQUEST ) diff --git a/src/user_session/models.py b/src/user_session/models.py deleted file mode 100644 index bcb664d..0000000 --- a/src/user_session/models.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.db import models -from user.models import User - -class UserSession(models.Model): - id = models.AutoField(primary_key=True) - user = models.ForeignKey( - User, related_name="user_sessions", on_delete=models.CASCADE - ) - session_token = models.CharField(max_length=40) - created_at = models.DateTimeField(auto_now_add=True) - - def __str__(self): - return f'{self.user.netid} - {self.session_token}' \ No newline at end of file diff --git a/src/user_session/serializers.py b/src/user_session/serializers.py deleted file mode 100644 index 7b232da..0000000 --- a/src/user_session/serializers.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework import serializers -from user_session.models import UserSession - -class UserSessionSerializer(serializers.ModelSerializer): - class Meta: - model = UserSession - fields = ['id', 'user', 'session_token', 'created_at'] \ No newline at end of file diff --git a/src/user_session/views.py b/src/user_session/views.py deleted file mode 100644 index d0b271a..0000000 --- a/src/user_session/views.py +++ /dev/null @@ -1,8 +0,0 @@ -from rest_framework import viewsets -from user_session.models import UserSession -from user_session.serializers import UserSessionSerializer - - -class UserSessionViewSet(viewsets.ModelViewSet): - queryset = UserSession.objects.all() - serializer_class = UserSessionSerializer From e07a03fa909d29780053710424619895a4729448 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Wed, 20 Nov 2024 17:25:06 -0500 Subject: [PATCH 281/305] create user not just session --- src/user/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/user/views.py b/src/user/views.py index 436dbdc..a795cd0 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -12,7 +12,7 @@ from user_session.models import UserSession import secrets import os - +import datetime class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() @@ -113,7 +113,7 @@ def login(self, request): session_token = secrets.token_hex(20) while UserSession.objects.filter(session_token=session_token).exists(): session_token = secrets.token_hex(20) - UserSession.objects.create(user=user) + UserSession.objects.create(user=user, session_token = session_token, created_at = datetime.now()) return Response({"session_token": session_token}, status=status.HTTP_200_OK) From 6709764d469e74a9077bb3157f6f79c47760fedb Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Wed, 20 Nov 2024 17:25:06 -0500 Subject: [PATCH 282/305] create user not just session --- src/user/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/user/views.py b/src/user/views.py index 436dbdc..a795cd0 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -12,7 +12,7 @@ from user_session.models import UserSession import secrets import os - +import datetime class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() @@ -113,7 +113,7 @@ def login(self, request): session_token = secrets.token_hex(20) while UserSession.objects.filter(session_token=session_token).exists(): session_token = secrets.token_hex(20) - UserSession.objects.create(user=user) + UserSession.objects.create(user=user, session_token = session_token, created_at = datetime.now()) return Response({"session_token": session_token}, status=status.HTTP_200_OK) From bf4f3f25efb05514a2f3797eadfd698058534c98 Mon Sep 17 00:00:00 2001 From: Skye Slattery <144565459+skyeslattery@users.noreply.github.com> Date: Sun, 24 Nov 2024 00:43:24 -0500 Subject: [PATCH 283/305] Change idToken to snake case --- src/user/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/user/views.py b/src/user/views.py index 34a84ae..b13b408 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -64,7 +64,7 @@ def remove_favorite_item(self, request, pk=None): @action(detail=False, methods=["post"], url_path="login") def login(self, request): device_token = request.data.get("device_token") - id_token_str = request.data.get("idToken") + id_token_str = request.data.get("id_token") if not device_token: return Response( @@ -152,15 +152,15 @@ def login(self, request): except ValueError: return Response( - {"error": "Invalid idToken"}, + {"error": "Invalid id_token"}, status=status.HTTP_400_BAD_REQUEST, ) else: - # no idToken provided, user is anonymous + # no id_token provided, user is anonymous if user.netid is None: pass else: - # device token associated with an authenticated user but no idToken provided + # device token associated with an authenticated user but no id_token provided return Response( {"error": "Device token associated with an authenticated user"}, status=status.HTTP_400_BAD_REQUEST, From a568423f6ea41dd6e1c5d58d6727008b3a053575 Mon Sep 17 00:00:00 2001 From: Skye Slattery <144565459+skyeslattery@users.noreply.github.com> Date: Sun, 24 Nov 2024 00:43:24 -0500 Subject: [PATCH 284/305] Change idToken to snake case --- src/user/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/user/views.py b/src/user/views.py index 34a84ae..b13b408 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -64,7 +64,7 @@ def remove_favorite_item(self, request, pk=None): @action(detail=False, methods=["post"], url_path="login") def login(self, request): device_token = request.data.get("device_token") - id_token_str = request.data.get("idToken") + id_token_str = request.data.get("id_token") if not device_token: return Response( @@ -152,15 +152,15 @@ def login(self, request): except ValueError: return Response( - {"error": "Invalid idToken"}, + {"error": "Invalid id_token"}, status=status.HTTP_400_BAD_REQUEST, ) else: - # no idToken provided, user is anonymous + # no id_token provided, user is anonymous if user.netid is None: pass else: - # device token associated with an authenticated user but no idToken provided + # device token associated with an authenticated user but no id_token provided return Response( {"error": "Device token associated with an authenticated user"}, status=status.HTTP_400_BAD_REQUEST, From ed0ce438eba3cdb537509ee55ed46916fef97bf6 Mon Sep 17 00:00:00 2001 From: Skye Slattery <144565459+skyeslattery@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:16:41 -0500 Subject: [PATCH 285/305] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8523ec4..f3e5800 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ This is the backend for Eatery Blue. - Full Swagger Docs API Specs can be found at /docs when running the server +## FA24 Members + +- Thomas Vignos +- Skye Slattery +- Cassidy Xu + ## SP24 Members - Thomas Vignos From c318fb2c1f7b5a5146dddd6b619ec76f034dbf44 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 25 Feb 2025 23:12:17 -0500 Subject: [PATCH 286/305] create new login route using id token - temporary --- src/user/views.py | 184 ++++++++++++---------------------------------- 1 file changed, 49 insertions(+), 135 deletions(-) diff --git a/src/user/views.py b/src/user/views.py index 2a00945..9389f80 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -1,17 +1,14 @@ from rest_framework import viewsets, status from rest_framework.response import Response from rest_framework.decorators import action -from user.models import User -from user.serializers import UserSerializer -from eatery.models import Eatery from django.shortcuts import get_object_or_404 -from rest_framework.response import Response -from google.oauth2 import id_token +from google.oauth2 import id_token as google_id_token from google.auth.transport import requests -from user.models import User -from device_token.models import DeviceToken import os -import datetime + +from user.models import User +from user.serializers import UserSerializer +from eatery.models import Eatery class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() @@ -41,11 +38,9 @@ def remove_favorite_eatery(self, request, pk=None): def add_favorite_item(self, request, pk=None): user = get_object_or_404(User, pk=pk) item_name = request.data.get("item_name") - if item_name and item_name not in user.favorite_items: user.favorite_items.append(item_name) user.save() - user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) @@ -53,144 +48,63 @@ def add_favorite_item(self, request, pk=None): def remove_favorite_item(self, request, pk=None): user = get_object_or_404(User, pk=pk) item_name = request.data.get("item_name") - if item_name in user.favorite_items: user.favorite_items.remove(item_name) user.save() - user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) @action(detail=False, methods=["post"], url_path="login") def login(self, request): - device_token = request.data.get("device_token") - id_token_str = request.data.get("id_token") + auth_header = request.headers.get("Authorization") + if auth_header and auth_header.startswith("Bearer "): + id_token_str = auth_header[7:] + else: + id_token_str = request.data.get("id_token") - if not device_token: - return Response( - {"error": "device_token is required"}, - status=status.HTTP_400_BAD_REQUEST, - ) + if not id_token_str: + return Response({"error": "id_token is required"}, + status=status.HTTP_400_BAD_REQUEST) try: - # create device token if doesnt exist - device_token_obj = DeviceToken.objects.get_or_create( - device_token=device_token, - defaults={'user': User.objects.create(netid=None)} + idinfo = google_id_token.verify_oauth2_token( + id_token_str, requests.Request(), os.getenv("GOOGLE_CLIENT_ID") ) - user = device_token_obj.user - except Exception as e: - return Response( - {"error": f"Error processing device token: {str(e)}"}, - status=status.HTTP_400_BAD_REQUEST, + if idinfo.get("iss") not in ["accounts.google.com", "https://accounts.google.com"]: + return Response({"error": "Invalid token issuer"}, + status=status.HTTP_401_UNAUTHORIZED) + + google_user_id = idinfo["sub"] + + # # ensure the email is a Cornell email. + # if not email.endswith("@cornell.edu"): + # return Response({"error": "Non-Cornell email used"}, + # status=status.HTTP_401_UNAUTHORIZED) + + + user, _ = User.objects.get_or_create( + google_id=google_user_id, + defaults={ + # "email": email, + "given_name": idinfo.get("given_name", ""), + "family_name": idinfo.get("family_name", ""), + # "netid": email.split("@")[0], + }, ) - if id_token_str: - try: - # verifies token - idinfo = id_token.verify_oauth2_token( - id_token_str, requests.Request(), os.getenv("GOOGLE_CLIENT_ID") - ) - - # ensures token is from Google - if idinfo["iss"] not in [ - "accounts.google.com", - "https://accounts.google.com", - ]: - return Response( - {"error": "Invalid token issuer"}, - status=status.HTTP_401_UNAUTHORIZED, - ) - - userid = idinfo["sub"] - email = idinfo["email"] - - # ensures email is Cornell email - if not email.endswith("@cornell.edu"): - return Response( - {"error": "Non-Cornell email used"}, - status=status.HTTP_401_UNAUTHORIZED, - ) - - # creates user if not exists - authenticated_user = User.objects.get_or_create( - google_id=userid, - defaults={ - "email": email, - "given_name": idinfo.get("given_name", ""), - "family_name": idinfo.get("family_name", ""), - "netid": email.split("@")[0], - }, - ) - - if user == authenticated_user: - pass - elif user.netid is None: - # was unauthorized user, update to authenticated user - - # copy favorites - authenticated_user.favorite_items = list( - set(authenticated_user.favorite_items + user.favorite_items) - ) - authenticated_user.favorite_eateries.add(*user.favorite_eateries.all()) - authenticated_user.save() - - # copy device token - device_token_obj.user = authenticated_user - device_token_obj.save() - - # delete unauthorized user after merging - user.delete() - else: - # device token is associated with a different user, should not happen - return Response( - {"error": "Device token is associated with another user"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - return Response({"device_token": device_token}, status=status.HTTP_200_OK) - - except ValueError: - return Response( - {"error": "Invalid id_token"}, - status=status.HTTP_400_BAD_REQUEST, - ) - else: - # no id_token provided, user is anonymous - if user.netid is None: - pass - else: - # device token associated with an authenticated user but no id_token provided - return Response( - {"error": "Device token associated with an authenticated user"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - return Response({"device_token": device_token}, status=status.HTTP_200_OK) - - - - @action(detail=False, methods=["post"], url_path="logout") - def logout(self, request): - device_token = request.data.get("device_token") - if not device_token: - return Response( - {"error": "Device token required"}, status=status.HTTP_400_BAD_REQUEST - ) - try: - # delete the device token - device_token_obj = DeviceToken.objects.get(device_token=device_token) - user = device_token_obj.user - device_token_obj.delete() + favorites = request.data.get("favorite_items") + if favorites and isinstance(favorites, list): + merged_favorites = list(set(user.favorite_items + favorites)) + user.favorite_items = merged_favorites - # if user was anonymous and has no device tokens, delete the user - if user.netid is None and not DeviceToken.objects.filter(user=user).exists(): - user.delete() + fcm_token = request.data.get("fcm_token") + if fcm_token: + user.fcm_token = fcm_token - return Response( - {"message": "Logged out successfully"}, status=status.HTTP_200_OK - ) - except DeviceToken.DoesNotExist: - return Response( - {"error": "Invalid device token"}, status=status.HTTP_400_BAD_REQUEST - ) + user.save() + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) + + except ValueError: + return Response({"error": "Invalid id_token"}, + status=status.HTTP_400_BAD_REQUEST) From 99dd961628d3f7dfde8c45700cd06091914205c4 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Mon, 3 Mar 2025 22:36:00 -0500 Subject: [PATCH 287/305] change /authorize to use GET API --- ..._email_remove_user_family_name_and_more.py | 43 +++++++ src/user/models.py | 7 +- src/user/serializers.py | 6 +- src/user/views.py | 107 +++++++++++------- 4 files changed, 110 insertions(+), 53 deletions(-) create mode 100644 src/user/migrations/0002_remove_user_email_remove_user_family_name_and_more.py diff --git a/src/user/migrations/0002_remove_user_email_remove_user_family_name_and_more.py b/src/user/migrations/0002_remove_user_email_remove_user_family_name_and_more.py new file mode 100644 index 0000000..40f7e97 --- /dev/null +++ b/src/user/migrations/0002_remove_user_email_remove_user_family_name_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 4.0 on 2025-03-04 03:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='user', + name='email', + ), + migrations.RemoveField( + model_name='user', + name='family_name', + ), + migrations.RemoveField( + model_name='user', + name='given_name', + ), + migrations.RemoveField( + model_name='user', + name='google_id', + ), + migrations.RemoveField( + model_name='user', + name='netid', + ), + migrations.AddField( + model_name='user', + name='device_id', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AddField( + model_name='user', + name='fcm_token', + field=models.CharField(blank=True, max_length=100), + ), + ] diff --git a/src/user/models.py b/src/user/models.py index 4a3d940..f1d7edf 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -3,11 +3,8 @@ class User(models.Model): - netid = models.CharField(max_length=10, null=True, blank=True) - given_name = models.CharField(max_length=30, null=True, blank=True) - family_name = models.CharField(max_length=30, null=True, blank=True) - google_id = models.CharField(max_length=50, unique=True, null=True, blank=True) - email = models.EmailField(max_length=255, null=True, blank=True) + device_id = models.CharField(max_length=100, blank=True) + fcm_token = models.CharField(max_length=100, blank=True) favorite_items = ArrayField( models.CharField(max_length=100), blank=True, default=list ) diff --git a/src/user/serializers.py b/src/user/serializers.py index 851cb05..cb80def 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -12,10 +12,8 @@ class Meta: model = User fields = [ "id", - "given_name", - "family_name", - "netid", - "google_id", + "device_id", + "fcm_token", "favorite_eateries", "favorite_items", ] diff --git a/src/user/views.py b/src/user/views.py index 9389f80..2646d62 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -2,8 +2,7 @@ from rest_framework.response import Response from rest_framework.decorators import action from django.shortcuts import get_object_or_404 -from google.oauth2 import id_token as google_id_token -from google.auth.transport import requests +import requests as http_requests import os from user.models import User @@ -54,57 +53,77 @@ def remove_favorite_item(self, request, pk=None): user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) - @action(detail=False, methods=["post"], url_path="login") - def login(self, request): + @action(detail=False, methods=["post"], url_path="authorize") + def authorize(self, request): auth_header = request.headers.get("Authorization") - if auth_header and auth_header.startswith("Bearer "): - id_token_str = auth_header[7:] - else: - id_token_str = request.data.get("id_token") - if not id_token_str: - return Response({"error": "id_token is required"}, + # header should be in the form "Bearer " + if not auth_header: + return Response({"error": "Missing authorization header"}, + status=status.HTTP_400_BAD_REQUEST) + if not auth_header.startswith("Bearer "): + return Response({"error": "Invalid authorization header - must start with 'Bearer '"}, + status=status.HTTP_400_BAD_REQUEST) + + session_id = auth_header[7:] + + device_id = request.data.get("deviceId") + pin = request.data.get("pin") + fcm_token = request.data.get("fcmToken") + + if not device_id or not pin: + return Response({"error": "deviceId, pin required"}, status=status.HTTP_400_BAD_REQUEST) + # prepare payload for GET API + payload = { + "method": "createPIN", + "params": { + "PIN": pin, + "deviceId": device_id, + "sessionId": session_id + } + } + + # call createPIN from GET API try: - idinfo = google_id_token.verify_oauth2_token( - id_token_str, requests.Request(), os.getenv("GOOGLE_CLIENT_ID") + get_response = http_requests.post( + "https://services.get.cbord.com/GETServices/services/json/user", + json=payload, + headers={"Content-Type": "application/json"} ) - if idinfo.get("iss") not in ["accounts.google.com", "https://accounts.google.com"]: - return Response({"error": "Invalid token issuer"}, + result = get_response.json() + except Exception as e: + return Response({"error": "Error communicating with GET API", "details": str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + """ + note: right now if session_id is invalid, response is "error": "4001|Session not found" + and this retuns 400 + """ + # return 401 if “Error: not validated” in result otherwise return 400 + if result.get("exception"): + if "not validated" in result.get("exception"): + return Response({"error": result.get("exception")}, status=status.HTTP_401_UNAUTHORIZED) + return Response({"error": result.get("exception")}, + status=status.HTTP_400_BAD_REQUEST) - google_user_id = idinfo["sub"] - - # # ensure the email is a Cornell email. - # if not email.endswith("@cornell.edu"): - # return Response({"error": "Non-Cornell email used"}, - # status=status.HTTP_401_UNAUTHORIZED) - - - user, _ = User.objects.get_or_create( - google_id=google_user_id, - defaults={ - # "email": email, - "given_name": idinfo.get("given_name", ""), - "family_name": idinfo.get("family_name", ""), - # "netid": email.split("@")[0], - }, - ) + favorites = request.data.get("favorite_items") - favorites = request.data.get("favorite_items") - if favorites and isinstance(favorites, list): - merged_favorites = list(set(user.favorite_items + favorites)) - user.favorite_items = merged_favorites + user, _ = User.objects.get_or_create( + device_id=device_id, + defaults={} + ) - fcm_token = request.data.get("fcm_token") - if fcm_token: - user.fcm_token = fcm_token + # merge favorites if they exist + if favorites and isinstance(favorites, list): + merged_favorites = list(set(user.favorite_items + favorites)) + user.favorite_items = merged_favorites - user.save() - user_data = UserSerializer(user).data - return Response(user_data, status=status.HTTP_200_OK) + if fcm_token: + user.fcm_token = fcm_token - except ValueError: - return Response({"error": "Invalid id_token"}, - status=status.HTTP_400_BAD_REQUEST) + user.save() + user_data = UserSerializer(user).data + return Response(user_data, status=status.HTTP_200_OK) \ No newline at end of file From 6ddf7cff04afce89ff86e3b5c802fa87087e72cf Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Mon, 3 Mar 2025 22:54:51 -0500 Subject: [PATCH 288/305] add /refresh --- src/user/views.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/user/views.py b/src/user/views.py index 2646d62..a5d4de9 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -126,4 +126,49 @@ def authorize(self, request): user.save() user_data = UserSerializer(user).data - return Response(user_data, status=status.HTTP_200_OK) \ No newline at end of file + return Response(user_data, status=status.HTTP_200_OK) + + @action(detail=False, methods=["post"], url_path="refresh") + def refresh(self, request): + device_id = request.data.get("deviceId") + pin = request.data.get("pin") + if not device_id or not pin: + return Response({"error": "deviceId and pin are required"}, + status=status.HTTP_400_BAD_REQUEST) + + payload = { + "method": "authenticatePIN", + "params": { + "systemCredentials": { + "domain": "", + "userName": "get_mobile", + "password": "NOTUSED" + }, + "deviceId": device_id, + "pin": pin + } + } + + # refresh sessionId with GET API's authenticatePIN method + try: + get_response = http_requests.post( + "https://services.get.cbord.com/GETServices/services/json/authentication", + json=payload, + headers={"Content-Type": "application/json"} + ) + result = get_response.json() + except Exception as e: + return Response({"error": "Error communicating with GET API", "details": str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + # if exception, return 401 and user should be fully logged out + if result.get("exception"): + return Response({"error": result.get("exception")}, + status=status.HTTP_401_UNAUTHORIZED) + + new_session_id = result.get("response") + if not new_session_id: + return Response({"error": "Failed to retrieve new sessionId"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + return Response({"sessionId": new_session_id}, status=status.HTTP_200_OK) \ No newline at end of file From f7e566c73d16c910cc97cbbba9601c0cb4ac1e9a Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 4 Mar 2025 17:32:42 -0500 Subject: [PATCH 289/305] add recent transactions route --- src/user/views.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/user/views.py b/src/user/views.py index a5d4de9..bebebe9 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -171,4 +171,56 @@ def refresh(self, request): return Response({"error": "Failed to retrieve new sessionId"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - return Response({"sessionId": new_session_id}, status=status.HTTP_200_OK) \ No newline at end of file + return Response({"sessionId": new_session_id}, status=status.HTTP_200_OK) + + @action(detail=False, methods=["post"], url_path="transactions") + def transactions(self, request): + auth_header = request.headers.get("Authorization") + if not auth_header or not auth_header.startswith("Bearer "): + return Response({"error": "Missing or invalid authorization header"}, + status=status.HTTP_400_BAD_REQUEST) + session_id = auth_header[7:] + + # note: gets 100 most recent transcations including meal swipes, so we dont + # have an exact number of BRB transactions. can change to date range if it + # becomes a problem + payload = { + "method": "retrieveTransactionHistoryWithinDateRange", + "params": { + "paymentSystemType": 0, + "queryCriteria": { + "maxReturnMostRecent": 100, + "newestDate": None, + "oldestDate": None, + "accountId": None + }, + "sessionId": session_id + } + } + + try: + get_response = http_requests.post( + "https://services.get.cbord.com/GETServices/services/json/commerce", + json=payload, + headers={"Content-Type": "application/json"} + ) + result = get_response.json() + except Exception as e: + return Response({"error": "Error communicating with GET API", "details": str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + # BRB transactions (tenderId == "000000449") + transactions = result.get("response", {}).get("transactions", []) + brb_transactions = [] + for txn in transactions: + account_name = txn.get("accountName", "") + if "City Bucks" in account_name or "Big Red Bucks" in account_name: + brb_transactions.append({ + "amount": txn.get("amount"), + "tenderId": txn.get("tenderId"), + "accountName": account_name, + "date": txn.get("postedDate"), + "location": txn.get("locationName") + }) + + return Response({"transactions": brb_transactions}, status=status.HTTP_200_OK) \ No newline at end of file From ebe88f02b597272c2b721055e3a8699bda725e00 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Tue, 4 Mar 2025 18:22:52 -0500 Subject: [PATCH 290/305] add accounts route to get brbs, city bucks, laundry and add to user model --- ...ser_brb_balance_user_city_bucks_balance.py | 23 +++++ .../migrations/0004_user_laundry_balance.py | 18 ++++ ...me_user_citybucks_account_name_and_more.py | 28 ++++++ ...count_name_user_city_bucks_account_name.py | 18 ++++ src/user/models.py | 7 ++ src/user/serializers.py | 6 ++ src/user/views.py | 91 ++++++++++++++++++- 7 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 src/user/migrations/0003_user_brb_balance_user_city_bucks_balance.py create mode 100644 src/user/migrations/0004_user_laundry_balance.py create mode 100644 src/user/migrations/0005_user_brb_account_name_user_citybucks_account_name_and_more.py create mode 100644 src/user/migrations/0006_rename_citybucks_account_name_user_city_bucks_account_name.py diff --git a/src/user/migrations/0003_user_brb_balance_user_city_bucks_balance.py b/src/user/migrations/0003_user_brb_balance_user_city_bucks_balance.py new file mode 100644 index 0000000..4d0afb9 --- /dev/null +++ b/src/user/migrations/0003_user_brb_balance_user_city_bucks_balance.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0 on 2025-03-04 22:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0002_remove_user_email_remove_user_family_name_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='brb_balance', + field=models.FloatField(default=0), + ), + migrations.AddField( + model_name='user', + name='city_bucks_balance', + field=models.FloatField(default=0), + ), + ] diff --git a/src/user/migrations/0004_user_laundry_balance.py b/src/user/migrations/0004_user_laundry_balance.py new file mode 100644 index 0000000..7f06ae3 --- /dev/null +++ b/src/user/migrations/0004_user_laundry_balance.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2025-03-04 22:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0003_user_brb_balance_user_city_bucks_balance'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='laundry_balance', + field=models.FloatField(default=0), + ), + ] diff --git a/src/user/migrations/0005_user_brb_account_name_user_citybucks_account_name_and_more.py b/src/user/migrations/0005_user_brb_account_name_user_citybucks_account_name_and_more.py new file mode 100644 index 0000000..f2f53dd --- /dev/null +++ b/src/user/migrations/0005_user_brb_account_name_user_citybucks_account_name_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.0 on 2025-03-04 23:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0004_user_laundry_balance'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='brb_account_name', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='user', + name='citybucks_account_name', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='user', + name='laundry_account_name', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/src/user/migrations/0006_rename_citybucks_account_name_user_city_bucks_account_name.py b/src/user/migrations/0006_rename_citybucks_account_name_user_city_bucks_account_name.py new file mode 100644 index 0000000..47c56bb --- /dev/null +++ b/src/user/migrations/0006_rename_citybucks_account_name_user_city_bucks_account_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0 on 2025-03-04 23:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0005_user_brb_account_name_user_citybucks_account_name_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='user', + old_name='citybucks_account_name', + new_name='city_bucks_account_name', + ), + ] diff --git a/src/user/models.py b/src/user/models.py index f1d7edf..40023cf 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -11,6 +11,13 @@ class User(models.Model): favorite_eateries = models.ManyToManyField( "eatery.Eatery", related_name="favorited_by", blank=True ) + brb_account_name = models.CharField(max_length=255, blank=True) + city_bucks_account_name = models.CharField(max_length=255, blank=True) + laundry_account_name = models.CharField(max_length=255, blank=True) + + brb_balance = models.FloatField(default=0) + city_bucks_balance = models.FloatField(default=0) + laundry_balance = models.FloatField(default=0) def __str__(self): return f"{self.netid}" diff --git a/src/user/serializers.py b/src/user/serializers.py index cb80def..5770d72 100644 --- a/src/user/serializers.py +++ b/src/user/serializers.py @@ -16,4 +16,10 @@ class Meta: "fcm_token", "favorite_eateries", "favorite_items", + "brb_balance", + "city_bucks_balance", + "laundry_balance", + "brb_account_name", + "city_bucks_account_name", + "laundry_account_name", ] diff --git a/src/user/views.py b/src/user/views.py index bebebe9..2875797 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -176,8 +176,11 @@ def refresh(self, request): @action(detail=False, methods=["post"], url_path="transactions") def transactions(self, request): auth_header = request.headers.get("Authorization") - if not auth_header or not auth_header.startswith("Bearer "): - return Response({"error": "Missing or invalid authorization header"}, + if not auth_header: + return Response({"error": "Missing authorization header"}, + status=status.HTTP_400_BAD_REQUEST) + if not auth_header.startswith("Bearer "): + return Response({"error": "Invalid authorization header - must start with 'Bearer '"}, status=status.HTTP_400_BAD_REQUEST) session_id = auth_header[7:] @@ -209,7 +212,7 @@ def transactions(self, request): return Response({"error": "Error communicating with GET API", "details": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - # BRB transactions (tenderId == "000000449") + # brb transactions (tenderId == "000000449") transactions = result.get("response", {}).get("transactions", []) brb_transactions = [] for txn in transactions: @@ -223,4 +226,84 @@ def transactions(self, request): "location": txn.get("locationName") }) - return Response({"transactions": brb_transactions}, status=status.HTTP_200_OK) \ No newline at end of file + return Response({"transactions": brb_transactions}, status=status.HTTP_200_OK) + + @action(detail=False, methods=["post"], url_path="accounts") + def accounts(self, request): + auth_header = request.headers.get("Authorization") + if not auth_header or not auth_header.startswith("Bearer "): + return Response({"error": "Missing or invalid authorization header"}, + status=status.HTTP_400_BAD_REQUEST) + session_id = auth_header[7:] + + device_id = request.data.get("deviceId") + if not device_id: + return Response({"error": "deviceId is required"}, + status=status.HTTP_400_BAD_REQUEST) + + payload = { + "method": "retrieveAccounts", + "params": { + "sessionId": session_id + } + } + + try: + get_response = http_requests.post( + "https://services.get.cbord.com/GETServices/services/json/commerce", + json=payload, + headers={"Content-Type": "application/json"} + ) + result = get_response.json() + except Exception as e: + return Response({"error": "Error communicating with GET API", "details": str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + accounts = result.get("response", {}).get("accounts", []) + brb_account = None + city_bucks_account = None + laundry_account = None + + for account in accounts: + display_name = account.get("accountDisplayName", "") + if "Big Red Bucks" in display_name and brb_account is None: + brb_account = account + # two city bucks accounts, one for GET which is not the main one + if "City Bucks" in display_name and "GET" not in display_name and city_bucks_account is None: + city_bucks_account = account + if "Laundry" in display_name and laundry_account is None: + laundry_account = account + if brb_account and city_bucks_account and laundry_account: + break + + try: + user = User.objects.get(device_id=device_id) + except User.DoesNotExist: + return Response({"error": "User not found for the given deviceId"}, status=status.HTTP_404_NOT_FOUND) + + # update brb balance and account name + if brb_account: + user.brb_balance = brb_account.get("balance", 0) + user.brb_account_name = brb_account.get("accountDisplayName", "") + else: + user.brb_account_name = None + + if city_bucks_account: + user.city_bucks_balance = city_bucks_account.get("balance", 0) + user.city_bucks_account_name = city_bucks_account.get("accountDisplayName", "") + else: + user.city_bucks_account_name = None + + if laundry_account: + user.laundry_balance = laundry_account.get("balance", 0) + user.laundry_account_name = laundry_account.get("accountDisplayName", "") + else: + user.laundry_account_name = None + + user.save() + + return Response({ + "brb": {"name": user.brb_account_name, "balance": user.brb_balance}, + "city_bucks": {"name": user.city_bucks_account_name, "balance": user.city_bucks_balance}, + "laundry": {"name": user.laundry_account_name, "balance": user.laundry_balance} + }, status=status.HTTP_200_OK) \ No newline at end of file From caadd4c6a6d121937790019fdd6cf47b2c7b2b66 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Fri, 14 Mar 2025 12:55:55 -0400 Subject: [PATCH 291/305] change transactions to get all transactions instead of only brb/city buck --- src/user/views.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/user/views.py b/src/user/views.py index 2875797..a798b0c 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -99,7 +99,7 @@ def authorize(self, request): """ note: right now if session_id is invalid, response is "error": "4001|Session not found" - and this retuns 400 + w/ 400 status code """ # return 401 if “Error: not validated” in result otherwise return 400 if result.get("exception"): @@ -212,19 +212,25 @@ def transactions(self, request): return Response({"error": "Error communicating with GET API", "details": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + if result.get("exception"): + if "not validated" in result.get("exception"): + return Response({"error": result.get("exception")}, + status=status.HTTP_401_UNAUTHORIZED) + return Response({"error": result.get("exception")}, + status=status.HTTP_400_BAD_REQUEST) + # brb transactions (tenderId == "000000449") transactions = result.get("response", {}).get("transactions", []) brb_transactions = [] for txn in transactions: account_name = txn.get("accountName", "") - if "City Bucks" in account_name or "Big Red Bucks" in account_name: - brb_transactions.append({ - "amount": txn.get("amount"), - "tenderId": txn.get("tenderId"), - "accountName": account_name, - "date": txn.get("postedDate"), - "location": txn.get("locationName") - }) + brb_transactions.append({ + "amount": txn.get("amount"), + "tenderId": txn.get("tenderId"), + "accountName": account_name, + "date": txn.get("postedDate"), + "location": txn.get("locationName") + }) return Response({"transactions": brb_transactions}, status=status.HTTP_200_OK) @@ -259,6 +265,14 @@ def accounts(self, request): return Response({"error": "Error communicating with GET API", "details": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + if result.get("exception"): + if "not validated" in result.get("exception"): + return Response({"error": result.get("exception")}, + status=status.HTTP_401_UNAUTHORIZED) + return Response({"error": result.get("exception")}, + status=status.HTTP_400_BAD_REQUEST) + + accounts = result.get("response", {}).get("accounts", []) brb_account = None city_bucks_account = None From 1ab0a9b07360f155a8a4c3e455e9ee1c37d84824 Mon Sep 17 00:00:00 2001 From: skyeslattery Date: Thu, 20 Mar 2025 21:58:50 -0400 Subject: [PATCH 292/305] factor out error handling and hide baseurl in envrc --- src/user/views.py | 97 ++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/src/user/views.py b/src/user/views.py index a798b0c..aebbfd3 100644 --- a/src/user/views.py +++ b/src/user/views.py @@ -13,6 +13,8 @@ class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer + CBORD_BASE_URL = os.getenv('CBORD_BASE_URL') + @action(detail=True, methods=["post"], url_path="eatery/add") def add_favorite_eatery(self, request, pk=None): user = get_object_or_404(User, pk=pk) @@ -52,20 +54,45 @@ def remove_favorite_item(self, request, pk=None): user.save() user_data = UserSerializer(user).data return Response(user_data, status=status.HTTP_200_OK) - - @action(detail=False, methods=["post"], url_path="authorize") - def authorize(self, request): + + def check_auth_header(self, request): + """ + Validate auth header and return session_id if valid + header should be in the form "Bearer " + """ auth_header = request.headers.get("Authorization") - - # header should be in the form "Bearer " + # if not auth_header: - return Response({"error": "Missing authorization header"}, - status=status.HTTP_400_BAD_REQUEST) + return None, Response({"error": "Missing authorization header"}, + status=status.HTTP_400_BAD_REQUEST) if not auth_header.startswith("Bearer "): - return Response({"error": "Invalid authorization header - must start with 'Bearer '"}, - status=status.HTTP_400_BAD_REQUEST) - + return None, Response({"error": "Invalid authorization header - must start with 'Bearer '"}, + status=status.HTTP_400_BAD_REQUEST) + session_id = auth_header[7:] + return session_id, None + + def handle_cbord_exception(self, result): + """ + Check for exceptions in cbord response + Returns Response object if exception, None otherwise + + note: right now if session_id is invalid, response is "error": "4001|Session not found" + w/ 400 status code + """ + if result.get("exception"): + if "not validated" in result.get("exception"): + return Response({"error": result.get("exception")}, + status=status.HTTP_401_UNAUTHORIZED) + return Response({"error": result.get("exception")}, + status=status.HTTP_400_BAD_REQUEST) + return None + + @action(detail=False, methods=["post"], url_path="authorize") + def authorize(self, request): + session_id, error = self.check_auth_header(request) + if error: + return error device_id = request.data.get("deviceId") pin = request.data.get("pin") @@ -88,7 +115,7 @@ def authorize(self, request): # call createPIN from GET API try: get_response = http_requests.post( - "https://services.get.cbord.com/GETServices/services/json/user", + f"{self.CBORD_BASE_URL}/user", json=payload, headers={"Content-Type": "application/json"} ) @@ -97,17 +124,9 @@ def authorize(self, request): return Response({"error": "Error communicating with GET API", "details": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - """ - note: right now if session_id is invalid, response is "error": "4001|Session not found" - w/ 400 status code - """ - # return 401 if “Error: not validated” in result otherwise return 400 - if result.get("exception"): - if "not validated" in result.get("exception"): - return Response({"error": result.get("exception")}, - status=status.HTTP_401_UNAUTHORIZED) - return Response({"error": result.get("exception")}, - status=status.HTTP_400_BAD_REQUEST) + exception_response = self.handle_cbord_exception(result) + if exception_response: + return exception_response favorites = request.data.get("favorite_items") @@ -152,7 +171,7 @@ def refresh(self, request): # refresh sessionId with GET API's authenticatePIN method try: get_response = http_requests.post( - "https://services.get.cbord.com/GETServices/services/json/authentication", + f"{self.CBORD_BASE_URL}/authentication", json=payload, headers={"Content-Type": "application/json"} ) @@ -175,14 +194,9 @@ def refresh(self, request): @action(detail=False, methods=["post"], url_path="transactions") def transactions(self, request): - auth_header = request.headers.get("Authorization") - if not auth_header: - return Response({"error": "Missing authorization header"}, - status=status.HTTP_400_BAD_REQUEST) - if not auth_header.startswith("Bearer "): - return Response({"error": "Invalid authorization header - must start with 'Bearer '"}, - status=status.HTTP_400_BAD_REQUEST) - session_id = auth_header[7:] + session_id, error = self.check_auth_header(request) + if error: + return error # note: gets 100 most recent transcations including meal swipes, so we dont # have an exact number of BRB transactions. can change to date range if it @@ -203,7 +217,7 @@ def transactions(self, request): try: get_response = http_requests.post( - "https://services.get.cbord.com/GETServices/services/json/commerce", + f"{self.CBORD_BASE_URL}/commerce", json=payload, headers={"Content-Type": "application/json"} ) @@ -212,12 +226,9 @@ def transactions(self, request): return Response({"error": "Error communicating with GET API", "details": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - if result.get("exception"): - if "not validated" in result.get("exception"): - return Response({"error": result.get("exception")}, - status=status.HTTP_401_UNAUTHORIZED) - return Response({"error": result.get("exception")}, - status=status.HTTP_400_BAD_REQUEST) + exception_response = self.handle_cbord_exception(result) + if exception_response: + return exception_response # brb transactions (tenderId == "000000449") transactions = result.get("response", {}).get("transactions", []) @@ -236,11 +247,9 @@ def transactions(self, request): @action(detail=False, methods=["post"], url_path="accounts") def accounts(self, request): - auth_header = request.headers.get("Authorization") - if not auth_header or not auth_header.startswith("Bearer "): - return Response({"error": "Missing or invalid authorization header"}, - status=status.HTTP_400_BAD_REQUEST) - session_id = auth_header[7:] + session_id, error = self.check_auth_header(request) + if error: + return error device_id = request.data.get("deviceId") if not device_id: @@ -256,7 +265,7 @@ def accounts(self, request): try: get_response = http_requests.post( - "https://services.get.cbord.com/GETServices/services/json/commerce", + f"{self.CBORD_BASE_URL}/commerce", json=payload, headers={"Content-Type": "application/json"} ) From 2fe7bfb0aeed2b4ab84828e8b0cad9d754ba05b0 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 20 Mar 2025 22:46:04 -0400 Subject: [PATCH 293/305] add freege, add sheets scraping --- secrets/.envrctemplate | 3 + src/eatery/datatype/Eatery.py | 1 + src/eatery/util/eatery_store.txt | 3 +- .../management/commands/populate_models.py | 59 ++++++ src/static_sources/external_eateries.json | 171 +++++++++++++++++- 5 files changed, 233 insertions(+), 4 deletions(-) diff --git a/secrets/.envrctemplate b/secrets/.envrctemplate index 1a0f3cf..336c7aa 100644 --- a/secrets/.envrctemplate +++ b/secrets/.envrctemplate @@ -7,6 +7,9 @@ export POSTGRES_HOST= export POSTGRES_PORT= export DJANGO_SECRET_KEY= export GOOGLE_APPLICATION_CREDENTIALS= +export GOOGLE_SHEETS_API_KEY= +export FREEGE_SHEET_ID= +export FREEGE_APPROVED_EMAILS= # Not used export CORNELL_VENDOR_TOKEN= diff --git a/src/eatery/datatype/Eatery.py b/src/eatery/datatype/Eatery.py index bcbe5f3..c798638 100644 --- a/src/eatery/datatype/Eatery.py +++ b/src/eatery/datatype/Eatery.py @@ -47,3 +47,4 @@ class EateryID(Enum): MORRISON_DINING = 39 NOVICKS_CAFE = 40 VET_CAFE = 41 + FREEGE = 46 diff --git a/src/eatery/util/eatery_store.txt b/src/eatery/util/eatery_store.txt index 9488a26..977a9e7 100644 --- a/src/eatery/util/eatery_store.txt +++ b/src/eatery/util/eatery_store.txt @@ -3,4 +3,5 @@ {"id": 35, "name": "Temple of Zeus", "menu_summary": "Coffee, pastries, sandwiches", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Zeus.jpg", "location": "Goldwin Smith Hall", "campus_area": "Central", "latitude": 42.449091, "longitude": -76.483414, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 36, "name": "Gimme Coffee", "menu_summary": "Coffee, pastries, tea", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Gimme-Coffee.jpg", "location": "Gates Hall", "campus_area": "Central", "latitude": 42.444958, "longitude": -76.481169, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} {"id": 37, "name": "Louie's Lunch", "menu_summary": "Burgers, fries, shakes", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Louies-Lunch.jpg", "location": "Across from Risley", "campus_area": "Central", "latitude": 42.45336, "longitude": -76.481225, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} -{"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} \ No newline at end of file +{"id": 38, "name": "Anabel's Grocery", "menu_summary": "Groceries, quick bites", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Anabels-Grocery.jpg", "location": "Anabel Taylor Hall", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": true, "online_order_url": null} +{"id": 46, "name": "Free Food Fridge", "menu_summary": "Free food", "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Freege.jpg", "location": "Anabel Taylor Hall Room 120", "campus_area": "Central", "latitude": 42.445061, "longitude": -76.485826, "payment_accepts_meal_swipes": false, "payment_accepts_brbs": false, "payment_accepts_cash": false, "online_order_url": null} \ No newline at end of file diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index 1127546..cbb521f 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -1,12 +1,15 @@ from django.core.management.base import BaseCommand from datetime import datetime import requests +from eatery.datatype.Eatery import EateryID from eatery.util.constants import CORNELL_DINING_URL from event.models import Event from eatery.controllers.populate_eatery import PopulateEateryController from event.controllers.populate_event import PopulateEventController from item.controllers.populate_item import PopulateItemController from category.controllers.populate_category import PopulateCategoryController +import os +import json class Command(BaseCommand): @@ -29,6 +32,60 @@ def get_json(self): response = response.json() json_eateries = response["data"]["eateries"] return json_eateries + + def update_freege_external_eatery(self): + GOOGLE_SHEETS_API_KEY = os.environ.get("GOOGLE_SHEETS_API_KEY") + FREEGE_SHEET_ID = os.environ.get("FREEGE_SHEET_ID") + FREEGE_APPROVED_EMAILS = os.environ.get("FREEGE_APPROVED_EMAILS") + if not GOOGLE_SHEETS_API_KEY: + print("GOOGLE_SHEETS_API_KEY not set, cannot update freege external eatery") + return + if not FREEGE_SHEET_ID: + print("FREEGE_SHEET_ID not set, cannot update freege external eatery") + return + if not FREEGE_APPROVED_EMAILS: + print("FREEGE_APPROVED_EMAILS not set, cannot update freege external eatery") + return + + approved_emails = FREEGE_APPROVED_EMAILS.split(",") + + try: + response = requests.get(f"https://sheets.googleapis.com/v4/spreadsheets/{FREEGE_SHEET_ID}/values/A2:M?key={GOOGLE_SHEETS_API_KEY}", timeout=10) + except Exception as e: + raise e + if response.status_code > 400: + return + + response = response.json() + freege_items = response["values"] + + freege_dining_items = [] + for item in freege_items: + # item[1] is the email + # item[4] is item name + if len(item) < 5: + continue + if item[1] not in approved_emails: + continue + + freege_dining_items.append({ + "item": item[4].strip(), + "healthy": False, + "category": "General", + }) + + with open( + "./static_sources/external_eateries.json", "r" + ) as external_eateries_file: + external_eateries_json = json.load(external_eateries_file) + for eatery in external_eateries_json["eateries"]: + if eatery["id"] == EateryID.FREEGE.value: + print("Updating freege external eatery") + eatery["diningItems"] = freege_dining_items + break + + with open("./static_sources/external_eateries.json", "w") as external_eateries_file: + json.dump(external_eateries_json, external_eateries_file, indent=2) def logger_wrapper(self, command_obj, log_title, args): pre = int(datetime.now().timestamp()) @@ -63,6 +120,8 @@ def process(self): json_eateries = self.get_json() + self.update_freege_external_eatery() + Event.truncate() self.logger_wrapper( diff --git a/src/static_sources/external_eateries.json b/src/static_sources/external_eateries.json index c927f6a..e88046b 100644 --- a/src/static_sources/external_eateries.json +++ b/src/static_sources/external_eateries.json @@ -139,7 +139,7 @@ "id": 34, "slug": "Macs", "external": true, - "name": "Mac's Café", + "name": "Mac's Caf\u00e9", "nameshort": "Mac's", "about": "", "cornellDining": false, @@ -360,7 +360,9 @@ ] } ], - "datesClosed": ["09/06/21"], + "datesClosed": [ + "09/06/21" + ], "payMethods": [ { "descr": "Cash", @@ -823,6 +825,169 @@ "category": "General" } ] + }, + { + "id": 46, + "slug": "Freege", + "external": true, + "name": "Free Food Fridge", + "nameshort": "Freege", + "about": "", + "cornellDining": false, + "contactPhone": "", + "latitude": 42.445061, + "longitude": -76.485826, + "campusArea": { + "descr": "South Campus", + "descrshort": "South" + }, + "location": "Anabel Taylor Hall Room 120", + "eateryTypes": [ + { + "descr": "Cash", + "descrshort": "Cash" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Saturday", + "events": [ + { + "descr": "General", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Sunday", + "events": [ + { + "descr": "General", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Free", + "descrshort": "Free" + } + ], + "diningItems": [ + { + "item": "Croissant", + "healthy": false, + "category": "General" + }, + { + "item": "Coffee Cake", + "healthy": false, + "category": "General" + }, + { + "item": "Filled Crossiant", + "healthy": false, + "category": "General" + }, + { + "item": "Cookies", + "healthy": false, + "category": "General" + }, + { + "item": "Asorted Scones", + "healthy": false, + "category": "General" + }, + { + "item": "Bufflao Chicken Wraps", + "healthy": false, + "category": "General" + }, + { + "item": "Turkey Sandwich", + "healthy": false, + "category": "General" + }, + { + "item": "Italian Combo", + "healthy": false, + "category": "General" + }, + { + "item": "Prosciutto Mozzarela", + "healthy": false, + "category": "General" + }, + { + "item": "Roast Beef & Asiago Cheese", + "healthy": false, + "category": "General" + } + ] } ] -} +} \ No newline at end of file From 4fc6f251f3b98c2c307bc33f3f38bcc711753e70 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 21 Mar 2025 20:03:22 -0400 Subject: [PATCH 294/305] update freedge --- secrets/.envrctemplate | 4 +-- src/eatery/datatype/Eatery.py | 2 +- .../management/commands/populate_models.py | 32 +++++++++---------- src/static_sources/external_eateries.json | 18 +++++------ 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/secrets/.envrctemplate b/secrets/.envrctemplate index 336c7aa..a8c1e43 100644 --- a/secrets/.envrctemplate +++ b/secrets/.envrctemplate @@ -8,8 +8,8 @@ export POSTGRES_PORT= export DJANGO_SECRET_KEY= export GOOGLE_APPLICATION_CREDENTIALS= export GOOGLE_SHEETS_API_KEY= -export FREEGE_SHEET_ID= -export FREEGE_APPROVED_EMAILS= +export FREEDGE_SHEET_ID= +export FREEDGE_APPROVED_EMAILS= # Not used export CORNELL_VENDOR_TOKEN= diff --git a/src/eatery/datatype/Eatery.py b/src/eatery/datatype/Eatery.py index c798638..f9d4679 100644 --- a/src/eatery/datatype/Eatery.py +++ b/src/eatery/datatype/Eatery.py @@ -47,4 +47,4 @@ class EateryID(Enum): MORRISON_DINING = 39 NOVICKS_CAFE = 40 VET_CAFE = 41 - FREEGE = 46 + FREEDGE = 46 diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index cbb521f..e569673 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -35,22 +35,22 @@ def get_json(self): def update_freege_external_eatery(self): GOOGLE_SHEETS_API_KEY = os.environ.get("GOOGLE_SHEETS_API_KEY") - FREEGE_SHEET_ID = os.environ.get("FREEGE_SHEET_ID") - FREEGE_APPROVED_EMAILS = os.environ.get("FREEGE_APPROVED_EMAILS") + FREEDGE_SHEET_ID = os.environ.get("FREEDGE_SHEET_ID") + FREEDGE_APPROVED_EMAILS = os.environ.get("FREEDGE_APPROVED_EMAILS") if not GOOGLE_SHEETS_API_KEY: print("GOOGLE_SHEETS_API_KEY not set, cannot update freege external eatery") return - if not FREEGE_SHEET_ID: - print("FREEGE_SHEET_ID not set, cannot update freege external eatery") + if not FREEDGE_SHEET_ID: + print("FREEDGE_SHEET_ID not set, cannot update freege external eatery") return - if not FREEGE_APPROVED_EMAILS: - print("FREEGE_APPROVED_EMAILS not set, cannot update freege external eatery") + if not FREEDGE_APPROVED_EMAILS: + print("FREEDGE_APPROVED_EMAILS not set, cannot update freege external eatery") return - approved_emails = FREEGE_APPROVED_EMAILS.split(",") + approved_emails = FREEDGE_APPROVED_EMAILS.split(",") try: - response = requests.get(f"https://sheets.googleapis.com/v4/spreadsheets/{FREEGE_SHEET_ID}/values/A2:M?key={GOOGLE_SHEETS_API_KEY}", timeout=10) + response = requests.get(f"https://sheets.googleapis.com/v4/spreadsheets/{FREEDGE_SHEET_ID}/values/A2:M?key={GOOGLE_SHEETS_API_KEY}", timeout=10) except Exception as e: raise e if response.status_code > 400: @@ -63,29 +63,29 @@ def update_freege_external_eatery(self): for item in freege_items: # item[1] is the email # item[4] is item name - if len(item) < 5: + # item[11] is if it is there + if len(item) < 12: continue - if item[1] not in approved_emails: + if item[1] not in approved_emails or item[11] != "Yes": continue freege_dining_items.append({ "item": item[4].strip(), "healthy": False, - "category": "General", + "category": "Free Food", }) with open( - "./static_sources/external_eateries.json", "r" + "./static_sources/external_eateries.json", "w+" ) as external_eateries_file: external_eateries_json = json.load(external_eateries_file) for eatery in external_eateries_json["eateries"]: - if eatery["id"] == EateryID.FREEGE.value: + if eatery["id"] == EateryID.FREEDGE.value: print("Updating freege external eatery") eatery["diningItems"] = freege_dining_items break - - with open("./static_sources/external_eateries.json", "w") as external_eateries_file: - json.dump(external_eateries_json, external_eateries_file, indent=2) + + json.dump(external_eateries_json, external_eateries_file, indent=2) def logger_wrapper(self, command_obj, log_title, args): pre = int(datetime.now().timestamp()) diff --git a/src/static_sources/external_eateries.json b/src/static_sources/external_eateries.json index e88046b..d2ba168 100644 --- a/src/static_sources/external_eateries.json +++ b/src/static_sources/external_eateries.json @@ -828,10 +828,10 @@ }, { "id": 46, - "slug": "Freege", + "slug": "Freedge", "external": true, "name": "Free Food Fridge", - "nameshort": "Freege", + "nameshort": "Freedge", "about": "", "cornellDining": false, "contactPhone": "", @@ -855,7 +855,7 @@ "weekday": "Monday", "events": [ { - "descr": "General", + "descr": "Free Food", "start": "08:30", "end": "23:00", "menu": [] @@ -866,7 +866,7 @@ "weekday": "Tuesday", "events": [ { - "descr": "General", + "descr": "Free Food", "start": "08:30", "end": "23:00", "menu": [] @@ -877,7 +877,7 @@ "weekday": "Wednesday", "events": [ { - "descr": "General", + "descr": "Free Food", "start": "08:30", "end": "23:00", "menu": [] @@ -888,7 +888,7 @@ "weekday": "Thursday", "events": [ { - "descr": "General", + "descr": "Free Food", "start": "08:30", "end": "23:00", "menu": [] @@ -899,7 +899,7 @@ "weekday": "Friday", "events": [ { - "descr": "General", + "descr": "Free Food", "start": "08:30", "end": "23:00", "menu": [] @@ -910,7 +910,7 @@ "weekday": "Saturday", "events": [ { - "descr": "General", + "descr": "Free Food", "start": "08:30", "end": "23:00", "menu": [] @@ -921,7 +921,7 @@ "weekday": "Sunday", "events": [ { - "descr": "General", + "descr": "Free Food", "start": "08:30", "end": "23:00", "menu": [] From b5be731b994b8451b18f7d75dbb8d3f1be23c6d6 Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri <68517064+Aayush-Agnihotri@users.noreply.github.com> Date: Fri, 28 Mar 2025 09:55:50 -0400 Subject: [PATCH 295/305] Update deploy-dev.yml --- .github/workflows/deploy-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 41679be..4db412d 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -42,4 +42,4 @@ jobs: docker stack rm thestack sleep 20s docker stack deploy -c docker-compose.yml thestack - docker system prune -a + y | docker system prune -a From 97250de0d7865d3a1776450cd3c49c8a3cfa441e Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 8 Apr 2025 13:24:44 -0400 Subject: [PATCH 296/305] add dietary preferences and allergens --- .gitignore | 1 + .../management/commands/populate_models.py | 26 +- src/item/controllers/populate_item.py | 9 +- ...etarypreference_item_allergens_and_more.py | 35 + src/item/models.py | 20 +- src/item/serializers.py | 23 +- src/static_sources/external_eateries.json | 73 +- .../external_eateries_static.json | 993 ++++++++++++++++++ 8 files changed, 1128 insertions(+), 52 deletions(-) create mode 100644 src/item/migrations/0003_allergen_dietarypreference_item_allergens_and_more.py create mode 100644 src/static_sources/external_eateries_static.json diff --git a/.gitignore b/.gitignore index 3ab25ee..52fc3ef 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ MANIFEST local_settings.py db.sqlite3 db.sqlite3-journal +src/static_sources/external_eateries.json diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index e569673..de47785 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -63,29 +63,33 @@ def update_freege_external_eatery(self): for item in freege_items: # item[1] is the email # item[4] is item name + # item[7] is allergens + # item[8] is dietary preferences # item[11] is if it is there if len(item) < 12: continue if item[1] not in approved_emails or item[11] != "Yes": continue - + freege_dining_items.append({ "item": item[4].strip(), "healthy": False, "category": "Free Food", + "dietaryPreferences": item[8].split(", ") if item[8] != "N/A" else [], + "allergens": item[7].split(", ") if item[7] != "N/A" else [], }) - with open( - "./static_sources/external_eateries.json", "w+" - ) as external_eateries_file: + with open("./static_sources/external_eateries_static.json", "r") as external_eateries_file: external_eateries_json = json.load(external_eateries_file) - for eatery in external_eateries_json["eateries"]: - if eatery["id"] == EateryID.FREEDGE.value: - print("Updating freege external eatery") - eatery["diningItems"] = freege_dining_items - break - - json.dump(external_eateries_json, external_eateries_file, indent=2) + + for eatery in external_eateries_json["eateries"]: + if eatery["id"] == EateryID.FREEDGE.value: + print("Updating freege external eatery") + eatery["diningItems"] = freege_dining_items + break + + with open("./static_sources/external_eateries.json", "w") as external_eateries_file: + json.dump(external_eateries_json, external_eateries_file, indent=2) def logger_wrapper(self, command_obj, log_title, args): pre = int(datetime.now().timestamp()) diff --git a/src/item/controllers/populate_item.py b/src/item/controllers/populate_item.py index 21364a4..34a26a9 100644 --- a/src/item/controllers/populate_item.py +++ b/src/item/controllers/populate_item.py @@ -15,7 +15,10 @@ def generate_cafe_items(self, menu, json_eatery): except KeyError: continue - data = {"category": category_id, "name": json_item["item"]} + dietary_preferences = json_item.get("dietaryPreferences", []) + allergens = json_item.get("allergens", []) + + data = {"category": category_id, "name": json_item["item"], "dietary_preferences": dietary_preferences, "allergens": allergens} item = ItemSerializer(data=data) if item.is_valid(): item.save() @@ -29,7 +32,9 @@ def generate_dining_hall_items(self, menu, json_event, json_eatery): category_id = menu[category_name] for json_item in json_menu["items"]: - data = {"category": category_id, "name": json_item["item"]} + dietary_preferences = json_item.get("dietaryPreferences", []) + allergens = json_item.get("allergens", []) + data = {"category": category_id, "name": json_item["item"], "dietary_preferences": dietary_preferences, "allergens": allergens} item = ItemSerializer(data=data) if item.is_valid(): item.save() diff --git a/src/item/migrations/0003_allergen_dietarypreference_item_allergens_and_more.py b/src/item/migrations/0003_allergen_dietarypreference_item_allergens_and_more.py new file mode 100644 index 0000000..49481aa --- /dev/null +++ b/src/item/migrations/0003_allergen_dietarypreference_item_allergens_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.0 on 2025-04-08 17:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('item', '0002_alter_item_id'), + ] + + operations = [ + migrations.CreateModel( + name='Allergen', + fields=[ + ('name', models.CharField(max_length=30, primary_key=True, serialize=False)), + ], + ), + migrations.CreateModel( + name='DietaryPreference', + fields=[ + ('name', models.CharField(max_length=30, primary_key=True, serialize=False)), + ], + ), + migrations.AddField( + model_name='item', + name='allergens', + field=models.ManyToManyField(blank=True, related_name='items', to='item.Allergen'), + ), + migrations.AddField( + model_name='item', + name='dietary_preferences', + field=models.ManyToManyField(blank=True, related_name='items', to='item.DietaryPreference'), + ), + ] diff --git a/src/item/models.py b/src/item/models.py index d0ac644..1ed640a 100644 --- a/src/item/models.py +++ b/src/item/models.py @@ -1,6 +1,18 @@ from django.db import models from category.models import Category +class DietaryPreference(models.Model): + name = models.CharField(max_length=30, primary_key=True) + + def __str__(self): + return self.name + +class Allergen(models.Model): + name = models.CharField(max_length=30, primary_key=True) + + def __str__(self): + return self.name + class Item(models.Model): category = models.ForeignKey( @@ -8,6 +20,12 @@ class Item(models.Model): ) name = models.CharField(max_length=40, default="Item") base_price = models.FloatField(null=True, blank=True, default=0.0) + dietary_preferences = models.ManyToManyField( + DietaryPreference, blank=True, related_name="items" + ) + allergens = models.ManyToManyField( + Allergen, blank=True, related_name="items" + ) def __str__(self): - return f"{self.name} ({self.category.id})" + return f"{self.name} ({self.category.id})" \ No newline at end of file diff --git a/src/item/serializers.py b/src/item/serializers.py index 620f86a..769ce15 100644 --- a/src/item/serializers.py +++ b/src/item/serializers.py @@ -1,21 +1,38 @@ from rest_framework import serializers -from item.models import Item +from item.models import Item, DietaryPreference, Allergen class ItemSerializer(serializers.ModelSerializer): id = serializers.IntegerField(read_only=True) name = serializers.CharField(default="Item") + dietary_preferences = serializers.ListField( + child=serializers.CharField(), allow_empty=True, default=[] + ) + allergens = serializers.ListField( + child=serializers.CharField(), allow_empty=True, default=[] + ) def create(self, validated_data): + dietary_prefs = validated_data.pop('dietary_preferences', []) + allergens = validated_data.pop('allergens', []) item, _ = Item.objects.get_or_create(**validated_data) + + for pref_name in dietary_prefs: + pref, _ = DietaryPreference.objects.get_or_create(name=pref_name) + item.dietary_preferences.add(pref) + + for allergen_name in allergens: + allergen, _ = Allergen.objects.get_or_create(name=allergen_name) + item.allergens.add(allergen) + return item class Meta: model = Item - fields = ["id", "category", "name"] + fields = ["id", "category", "name", "dietary_preferences", "allergens"] class ItemSerializerOptimized(serializers.ModelSerializer): class Meta: model = Item - fields = ["id", "name"] + fields = ["id", "name", "dietary_preferences", "allergens"] diff --git a/src/static_sources/external_eateries.json b/src/static_sources/external_eateries.json index d2ba168..baafc30 100644 --- a/src/static_sources/external_eateries.json +++ b/src/static_sources/external_eateries.json @@ -938,54 +938,57 @@ ], "diningItems": [ { - "item": "Croissant", + "item": "Fat-Free Milk", "healthy": false, - "category": "General" - }, - { - "item": "Coffee Cake", - "healthy": false, - "category": "General" - }, - { - "item": "Filled Crossiant", - "healthy": false, - "category": "General" - }, - { - "item": "Cookies", - "healthy": false, - "category": "General" - }, - { - "item": "Asorted Scones", - "healthy": false, - "category": "General" - }, - { - "item": "Bufflao Chicken Wraps", - "healthy": false, - "category": "General" + "category": "Free Food", + "dietaryPreferences": [], + "allergens": [ + "Dairy" + ] }, { - "item": "Turkey Sandwich", + "item": "Chicken Caesar Wrap", "healthy": false, - "category": "General" + "category": "Free Food", + "dietaryPreferences": [], + "allergens": [ + "Dairy", + "Shellfish" + ] }, { - "item": "Italian Combo", + "item": "Prosciutto & Mozzarella", "healthy": false, - "category": "General" + "category": "Free Food", + "dietaryPreferences": [], + "allergens": [ + "Soy", + "Nuts", + "Dairy", + "Shellfish" + ] }, { - "item": "Prosciutto Mozzarela", + "item": "Roast Beef & Asiago Cheese", "healthy": false, - "category": "General" + "category": "Free Food", + "dietaryPreferences": [], + "allergens": [ + "Soy", + "Nuts", + "Dairy" + ] }, { - "item": "Roast Beef & Asiago Cheese", + "item": "Roasted Turkey & Swiss", "healthy": false, - "category": "General" + "category": "Free Food", + "dietaryPreferences": [], + "allergens": [ + "Soy", + "Nuts", + "Dairy" + ] } ] } diff --git a/src/static_sources/external_eateries_static.json b/src/static_sources/external_eateries_static.json new file mode 100644 index 0000000..d2ba168 --- /dev/null +++ b/src/static_sources/external_eateries_static.json @@ -0,0 +1,993 @@ +{ + "eateries": [ + { + "id": 33, + "slug": "Terrace", + "external": true, + "name": "Terrace Restaurant", + "nameshort": "Terrace", + "about": "", + "contactPhone": "1-800-541-2501", + "latitude": 42.446267, + "longitude": -76.482314, + "location": "Statler", + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "10:00", + "end": "15:00", + "menu": [] + } + ] + } + ], + "datesClosed": [ + "9/6/21", + "11/25/21", + "11/26/21", + "11/27/21", + "11/28/21", + "12/20/21-1/20/22" + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + }, + { + "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", + "descrshort": "Meal Plan - Debit" + } + ], + "diningItems": [ + { + "item": "Salads", + "healthy": true, + "category": "General" + }, + { + "item": "Burrito Bowl", + "healthy": false, + "category": "General" + }, + { + "item": "Burrito", + "healthy": true, + "category": "General" + }, + { + "item": "Chicken Tenders", + "healthy": false, + "category": "General" + }, + { + "item": "Fries", + "healthy": false, + "category": "General" + }, + { + "item": "Pho", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 34, + "slug": "Macs", + "external": true, + "name": "Mac's Caf\u00e9", + "nameshort": "Mac's", + "about": "", + "cornellDining": false, + "contactPhone": "1-800-541-2501", + "latitude": 42.445921, + "longitude": -76.481984, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Statler Hotel", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "09:30", + "end": "17:30", + "menu": [] + } + ] + } + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + }, + { + "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", + "descrshort": "Meal Plan - Debit" + } + ], + "datesClosed": [ + "8/18/21", + "8/19/21", + "8/20/21", + "8/21/21", + "8/22/21", + "8/23/21", + "9/6/21", + "10/9/21", + "10/10/21", + "10/11/21", + "10/12/21", + "11/24/21", + "11/25/21", + "11/26/21", + "11/27/21", + "11/28/21", + "12/16/21", + "12/17/21", + "12/20/21-1/20/22" + ], + "diningItems": [ + { + "item": "Pizza", + "healthy": false, + "category": "General" + }, + { + "item": "Pasta", + "healthy": false, + "category": "General" + }, + { + "item": "Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Sushi to Go", + "healthy": false, + "category": "General" + }, + { + "item": "Soft Drinks", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 35, + "slug": "Zeus", + "external": true, + "name": "Temple of Zeus", + "nameshort": "Temple of Zeus", + "about": "", + "cornellDining": false, + "contactPhone": "", + "latitude": 42.449091, + "longitude": -76.483414, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Goldwin Smith Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "17:00", + "menu": [] + } + ] + } + ], + "datesClosed": [ + "09/06/21" + ], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Sandwiches", + "healthy": true, + "category": "General" + }, + { + "item": "Soups", + "healthy": true, + "category": "General" + }, + { + "item": "Baked Goods", + "healthy": true, + "category": "General" + }, + { + "item": "Candy", + "healthy": false, + "category": "General" + }, + { + "item": "Soft Drinks", + "healthy": true, + "category": "General" + } + ] + }, + { + "id": 36, + "slug": "Gimme-Coffee", + "external": true, + "name": "Gimme Coffee", + "nameshort": "Gimme Coffee", + "about": "", + "cornellDining": false, + "contactPhone": "1-607-227-5391", + "latitude": 42.444958, + "longitude": -76.481169, + "campusArea": { + "descr": "Central Campus", + "descrshort": "Central" + }, + "location": "Gates Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "08:00", + "end": "15:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Coffee", + "healthy": true, + "category": "General" + }, + { + "item": "Baked Goods", + "healthy": true, + "category": "General" + } + ] + }, + { + "id": 37, + "slug": "Louies-Lunch", + "external": true, + "name": "Louie's Lunch", + "nameshort": "Louie's", + "about": "", + "cornellDining": false, + "contactPhone": "1-607-257-4649", + "latitude": 42.45336, + "longitude": -76.481225, + "campusArea": { + "descr": "North Campus", + "descrshort": "North" + }, + "location": "Across from Risley", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "11:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Saturday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "03:00", + "menu": [] + } + ] + }, + { + "weekday": "Sunday", + "events": [ + { + "descr": "General", + "start": "18:00", + "end": "00:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "French Fries", + "healthy": false, + "category": "General" + }, + { + "item": "Garlic Bread", + "healthy": false, + "category": "General" + }, + { + "item": "Cold Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Subs", + "healthy": false, + "category": "General" + }, + { + "item": "Pizza Subs", + "healthy": false, + "category": "General" + }, + { + "item": "Burgers", + "healthy": false, + "category": "General" + }, + { + "item": "Egg Sandwiches", + "healthy": false, + "category": "General" + }, + { + "item": "Hot Dogs", + "healthy": false, + "category": "General" + }, + { + "item": "Wraps", + "healthy": false, + "category": "General" + }, + { + "item": "Salads", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 38, + "slug": "Anabels-Grocery", + "external": true, + "name": "Anabel's Grocery", + "nameshort": "Anabel's", + "about": "", + "cornellDining": false, + "contactPhone": "", + "latitude": 42.445061, + "longitude": -76.485826, + "campusArea": { + "descr": "South Campus", + "descrshort": "South" + }, + "location": "Anabel Taylor Hall", + "eateryTypes": [ + { + "descr": "Cafe", + "descrshort": "cafe" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Wednesday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "19:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "19:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "19:00", + "menu": [] + } + ] + }, + { + "weekday": "Saturday", + "events": [ + { + "descr": "General", + "start": "12:00", + "end": "15:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Cash", + "descrshort": "Cash" + }, + { + "descr": "Cornell Card", + "descrshort": "Cornell Card" + }, + { + "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", + "descrshort": "Major Credit Cards" + } + ], + "diningItems": [ + { + "item": "Whole Grains", + "healthy": true, + "category": "General" + }, + { + "item": "Spices", + "healthy": false, + "category": "General" + }, + { + "item": "Legumes", + "healthy": true, + "category": "General" + }, + { + "item": "Fresh and Frozen Produce", + "healthy": true, + "category": "General" + }, + { + "item": "Nuts", + "healthy": true, + "category": "General" + }, + { + "item": "Dried Fruits", + "healthy": true, + "category": "General" + }, + { + "item": "Tofu", + "healthy": true, + "category": "General" + }, + { + "item": "Eggs", + "healthy": false, + "category": "General" + }, + { + "item": "Milks", + "healthy": true, + "category": "General" + }, + { + "item": "Kombucha on Tap", + "healthy": true, + "category": "General" + }, + { + "item": "Bottled Drinks", + "healthy": false, + "category": "General" + }, + { + "item": "Bulk Items", + "healthy": false, + "category": "General" + } + ] + }, + { + "id": 46, + "slug": "Freedge", + "external": true, + "name": "Free Food Fridge", + "nameshort": "Freedge", + "about": "", + "cornellDining": false, + "contactPhone": "", + "latitude": 42.445061, + "longitude": -76.485826, + "campusArea": { + "descr": "South Campus", + "descrshort": "South" + }, + "location": "Anabel Taylor Hall Room 120", + "eateryTypes": [ + { + "descr": "Cash", + "descrshort": "Cash" + } + ], + "onlineOrdering": false, + "onlineOrderUrl": null, + "operatingHours": [ + { + "weekday": "Monday", + "events": [ + { + "descr": "Free Food", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Tuesday", + "events": [ + { + "descr": "Free Food", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Wednesday", + "events": [ + { + "descr": "Free Food", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Thursday", + "events": [ + { + "descr": "Free Food", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Friday", + "events": [ + { + "descr": "Free Food", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Saturday", + "events": [ + { + "descr": "Free Food", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + }, + { + "weekday": "Sunday", + "events": [ + { + "descr": "Free Food", + "start": "08:30", + "end": "23:00", + "menu": [] + } + ] + } + ], + "datesClosed": [], + "payMethods": [ + { + "descr": "Free", + "descrshort": "Free" + } + ], + "diningItems": [ + { + "item": "Croissant", + "healthy": false, + "category": "General" + }, + { + "item": "Coffee Cake", + "healthy": false, + "category": "General" + }, + { + "item": "Filled Crossiant", + "healthy": false, + "category": "General" + }, + { + "item": "Cookies", + "healthy": false, + "category": "General" + }, + { + "item": "Asorted Scones", + "healthy": false, + "category": "General" + }, + { + "item": "Bufflao Chicken Wraps", + "healthy": false, + "category": "General" + }, + { + "item": "Turkey Sandwich", + "healthy": false, + "category": "General" + }, + { + "item": "Italian Combo", + "healthy": false, + "category": "General" + }, + { + "item": "Prosciutto Mozzarela", + "healthy": false, + "category": "General" + }, + { + "item": "Roast Beef & Asiago Cheese", + "healthy": false, + "category": "General" + } + ] + } + ] +} \ No newline at end of file From 416432ba9afcbd011271d4ff9312cec47b188d8c Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri <68517064+Aayush-Agnihotri@users.noreply.github.com> Date: Mon, 14 Apr 2025 16:53:45 -0400 Subject: [PATCH 297/305] Update deploy-dev.yml --- .github/workflows/deploy-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 4db412d..3e7fc06 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -42,4 +42,4 @@ jobs: docker stack rm thestack sleep 20s docker stack deploy -c docker-compose.yml thestack - y | docker system prune -a + yes | docker system prune -a From f72ec9f958108627096d33d11c5b68232b0bc87a Mon Sep 17 00:00:00 2001 From: Aayush Agnihotri <68517064+Aayush-Agnihotri@users.noreply.github.com> Date: Mon, 14 Apr 2025 16:54:02 -0400 Subject: [PATCH 298/305] Update deploy-prod.yml --- .github/workflows/deploy-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 23e3e41..4ecf75f 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -42,4 +42,4 @@ jobs: docker stack rm thestack sleep 20s docker stack deploy -c docker-compose.yml thestack - docker system prune -a + yes | docker system prune -a From b104eb58791f7a569e7928c2799957d2c04632fb Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 15 Apr 2025 11:35:40 -0400 Subject: [PATCH 299/305] Update populate_models.py --- .../management/commands/populate_models.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index de47785..1f62950 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -33,18 +33,18 @@ def get_json(self): json_eateries = response["data"]["eateries"] return json_eateries - def update_freege_external_eatery(self): + def update_freedge_external_eatery(self): GOOGLE_SHEETS_API_KEY = os.environ.get("GOOGLE_SHEETS_API_KEY") FREEDGE_SHEET_ID = os.environ.get("FREEDGE_SHEET_ID") FREEDGE_APPROVED_EMAILS = os.environ.get("FREEDGE_APPROVED_EMAILS") if not GOOGLE_SHEETS_API_KEY: - print("GOOGLE_SHEETS_API_KEY not set, cannot update freege external eatery") + print("GOOGLE_SHEETS_API_KEY not set, cannot update freedge external eatery") return if not FREEDGE_SHEET_ID: - print("FREEDGE_SHEET_ID not set, cannot update freege external eatery") + print("FREEDGE_SHEET_ID not set, cannot update freedge external eatery") return if not FREEDGE_APPROVED_EMAILS: - print("FREEDGE_APPROVED_EMAILS not set, cannot update freege external eatery") + print("FREEDGE_APPROVED_EMAILS not set, cannot update freedge external eatery") return approved_emails = FREEDGE_APPROVED_EMAILS.split(",") @@ -57,6 +57,10 @@ def update_freege_external_eatery(self): return response = response.json() + if "values" not in response: + print("No values in response, cannot update freedge external eatery") + print(response) + return freege_items = response["values"] freege_dining_items = [] From 0622acd9d282a8cbaa5bfdac4f5fd84610ffc580 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 15 Apr 2025 14:57:33 -0400 Subject: [PATCH 300/305] Update populate_models.py --- src/eatery_blue_backend/management/commands/populate_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eatery_blue_backend/management/commands/populate_models.py b/src/eatery_blue_backend/management/commands/populate_models.py index 1f62950..89e1d13 100644 --- a/src/eatery_blue_backend/management/commands/populate_models.py +++ b/src/eatery_blue_backend/management/commands/populate_models.py @@ -128,7 +128,7 @@ def process(self): json_eateries = self.get_json() - self.update_freege_external_eatery() + self.update_freedge_external_eatery() Event.truncate() From 4109cbec29d461a4dc5abddd18bd99a6853dd48e Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 17 Apr 2025 11:24:28 -0400 Subject: [PATCH 301/305] freedge --- src/eatery/controllers/populate_eatery.py | 11 ++-- src/eatery/util/constants.py | 75 +++++++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index 712e245..dce241a 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -1,5 +1,5 @@ import json -from eatery.util.constants import dining_id_to_internal_id, SnapshotFileName +from eatery.util.constants import dining_id_to_internal_id, internal_id_to_image_url, SnapshotFileName from eatery.serializers import EaterySerializer from eatery.models import Eatery from django.core.exceptions import ObjectDoesNotExist @@ -13,10 +13,11 @@ def generate_eatery(self, json_eatery): """ Create Eatery object from an eatery json from CornellDiningNow, and add to Eatery table. """ - eatery_id = dining_id_to_internal_id(json_eatery["id"]).value + eatery_id = dining_id_to_internal_id(json_eatery["id"]) data = { - "id": eatery_id, + "id": eatery_id.value, "name": json_eatery["name"], + "image_url": internal_id_to_image_url(eatery_id), "campus_area": json_eatery["campusArea"]["descrshort"], "latitude": json_eatery["latitude"], "longitude": json_eatery["longitude"], @@ -37,7 +38,7 @@ def generate_eatery(self, json_eatery): "online_order_url": json_eatery["onlineOrderUrl"], } try: - object = Eatery.objects.get(id=int(eatery_id)) + object = Eatery.objects.get(id=int(eatery_id.value)) except ObjectDoesNotExist: """ Create a new Eatery object @@ -91,4 +92,4 @@ def process(self, json_eateries): for json_eatery in json_eateries: self.generate_eatery(json_eatery) - self.add_eatery_store() + self.add_eatery_store() \ No newline at end of file diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index e9690e7..db68ec7 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -188,3 +188,78 @@ def vendor_name_to_internal_id(vendor_eatery_name): else: # TODO: Add a slack notif / flag that a wait time location was not recognized return None + +def internal_id_to_image_url(id: EateryID): + if id == EateryID.ONE_ZERO_FOUR_WEST: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg" + elif id == EateryID.LIBE_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg" + elif id == EateryID.ATRIUM_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg" + elif id == EateryID.BEAR_NECESSITIES: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg" + elif id == EateryID.BECKER_HOUSE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg" + elif id == EateryID.BIG_RED_BARN: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg" + elif id == EateryID.BUS_STOP_BAGELS: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg" + elif id == EateryID.CAFE_JENNIE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg" + elif id == EateryID.COOK_HOUSE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg" + elif id == EateryID.DAIRY_BAR: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg" + elif id == EateryID.CROSSINGS_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg" + elif id == EateryID.FRANNYS: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg" + elif id == EateryID.GOLDIES_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg" + elif id == EateryID.GREEN_DRAGON: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg" + elif id == EateryID.HOT_DOG_CART: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg" + elif id == EateryID.ICE_CREAM_BIKE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg" + elif id == EateryID.BETHE_HOUSE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg" + elif id == EateryID.JANSENS_MARKET: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg" + elif id == EateryID.KEETON_HOUSE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg" + elif id == EateryID.MANN_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg" + elif id == EateryID.MARTHAS_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg" + elif id == EateryID.MATTINS_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg" + elif id == EateryID.MCCORMICKS: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg" + elif id == EateryID.NORTH_STAR_DINING: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg" + elif id == EateryID.OKENSHIELDS: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg" + elif id == EateryID.RISLEY: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg" + elif id == EateryID.ROSE_HOUSE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg" + elif id == EateryID.RUSTYS: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg" + elif id == EateryID.STRAIGHT_FROM_THE_MARKET: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg" + elif id == EateryID.TRILLIUM: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg" + elif id == EateryID.MORRISON_DINING: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg" + elif id == EateryID.NOVICKS_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/novicks-cafe.jpg" + elif id == EateryID.VET_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/vets-cafe.jpg" + elif id == EateryID.MACS_CAFE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Macs-Cafe.jpg" + elif id == EateryID.TERRACE: + return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg" + else: + print(f"Missing image url for eatery_id {id}") + return "https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg" \ No newline at end of file From ded447ab5ebceff91088989aaaf066f5ddcaea10 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 17 Apr 2025 15:17:21 -0400 Subject: [PATCH 302/305] dictionary mappings --- src/eatery/util/constants.py | 358 +++++++++------------- src/static_sources/external_eateries.json | 57 ++-- 2 files changed, 172 insertions(+), 243 deletions(-) diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index db68ec7..2b04c78 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -36,230 +36,148 @@ class SnapshotFileName(Enum): SCHEDULE_EXCEPTION = "schedule_exception.txt" +# Dictionary mapping dining IDs to internal IDs +DINING_ID_TO_INTERNAL = { + 31: EateryID.ONE_ZERO_FOUR_WEST, + 7: EateryID.LIBE_CAFE, + 8: EateryID.ATRIUM_CAFE, + 1: EateryID.BEAR_NECESSITIES, + 25: EateryID.BECKER_HOUSE, + 10: EateryID.BIG_RED_BARN, + 11: EateryID.BUS_STOP_BAGELS, + 12: EateryID.CAFE_JENNIE, + 26: EateryID.COOK_HOUSE, + 14: EateryID.DAIRY_BAR, + 41: EateryID.CROSSINGS_CAFE, + 32: EateryID.FRANNYS, + 16: EateryID.GOLDIES_CAFE, + 15: EateryID.GREEN_DRAGON, + 24: EateryID.HOT_DOG_CART, + 34: EateryID.ICE_CREAM_BIKE, + 27: EateryID.BETHE_HOUSE, + 28: EateryID.JANSENS_MARKET, + 29: EateryID.KEETON_HOUSE, + 42: EateryID.MANN_CAFE, + 18: EateryID.MARTHAS_CAFE, + 19: EateryID.MATTINS_CAFE, + 33: EateryID.MCCORMICKS, + 3: EateryID.NORTH_STAR_DINING, + 20: EateryID.OKENSHIELDS, + 4: EateryID.RISLEY, + 5: EateryID.RPCC, + 30: EateryID.ROSE_HOUSE, + 21: EateryID.RUSTYS, + 13: EateryID.STRAIGHT_FROM_THE_MARKET, + 23: EateryID.TRILLIUM, + 43: EateryID.MORRISON_DINING, + 44: EateryID.NOVICKS_CAFE, + 45: EateryID.VET_CAFE, +} + +# Dictionary mapping vendor names to internal IDs +VENDOR_NAME_TO_INTERNAL = { + "bearnecessities": EateryID.BEAR_NECESSITIES, + "northstarmarketplace": EateryID.NORTH_STAR_DINING, + "jansensmarket": EateryID.JANSENS_MARKET, + "stockinghallcafe": EateryID.DAIRY_BAR, + "stockinghall": EateryID.DAIRY_BAR, + "marthas": EateryID.MARTHAS_CAFE, + "cafejennie": EateryID.CAFE_JENNIE, + "goldiescafe": EateryID.GOLDIES_CAFE, + "alicecookhouse": EateryID.COOK_HOUSE, + "carlbeckerhouse": EateryID.BECKER_HOUSE, + "duffield": EateryID.MATTINS_CAFE, + "greendragon": EateryID.GREEN_DRAGON, + "trillium": EateryID.TRILLIUM, + "olinlibecafe": EateryID.LIBE_CAFE, + "statlerterrace": EateryID.TERRACE, + "busstopbagels": EateryID.BUS_STOP_BAGELS, + "kosher": EateryID.ONE_ZERO_FOUR_WEST, + "jansensatbethehouse": EateryID.BETHE_HOUSE, + "keetonhouse": EateryID.KEETON_HOUSE, + "rpme": EateryID.RPCC, + "rosehouse": EateryID.ROSE_HOUSE, + "risley": EateryID.RISLEY, + "frannysft": EateryID.FRANNYS, + "mccormicks": EateryID.MCCORMICKS, + "sage": EateryID.ATRIUM_CAFE, + "straightmarket": EateryID.STRAIGHT_FROM_THE_MARKET, + "crossingscafe": EateryID.CROSSINGS_CAFE, + "okenshields": EateryID.OKENSHIELDS, + "bigredbarn": EateryID.BIG_RED_BARN, + "rustys": EateryID.RUSTYS, + "manncafe": EateryID.MANN_CAFE, + "statlermacs": EateryID.MACS_CAFE, + "morrisondining": EateryID.MORRISON_DINING, + "morrison": EateryID.MORRISON_DINING, + "novickscafe": EateryID.NOVICKS_CAFE, + "Vet College Cafe": EateryID.VET_CAFE, +} + +# Dictionary mapping internal IDs to image URLs +INTERNAL_ID_TO_IMAGE_URL = { + EateryID.ONE_ZERO_FOUR_WEST: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg", + EateryID.LIBE_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg", + EateryID.ATRIUM_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg", + EateryID.BEAR_NECESSITIES: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg", + EateryID.BECKER_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg", + EateryID.BIG_RED_BARN: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg", + EateryID.BUS_STOP_BAGELS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg", + EateryID.CAFE_JENNIE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg", + EateryID.COOK_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg", + EateryID.DAIRY_BAR: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg", + EateryID.CROSSINGS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg", + EateryID.FRANNYS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg", + EateryID.GOLDIES_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg", + EateryID.GREEN_DRAGON: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg", + EateryID.HOT_DOG_CART: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg", + EateryID.ICE_CREAM_BIKE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg", + EateryID.BETHE_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg", + EateryID.JANSENS_MARKET: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg", + EateryID.KEETON_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg", + EateryID.MANN_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg", + EateryID.MARTHAS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg", + EateryID.MATTINS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg", + EateryID.MCCORMICKS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg", + EateryID.NORTH_STAR_DINING: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg", + EateryID.OKENSHIELDS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg", + EateryID.RISLEY: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg", + EateryID.ROSE_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg", + EateryID.RUSTYS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg", + EateryID.STRAIGHT_FROM_THE_MARKET: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg", + EateryID.TRILLIUM: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg", + EateryID.MORRISON_DINING: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg", + EateryID.NOVICKS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/novicks-cafe.jpg", + EateryID.VET_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/vets-cafe.jpg", + EateryID.MACS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Macs-Cafe.jpg", + EateryID.TERRACE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg", +} + + def dining_id_to_internal_id(id: int): - if id == 31: - return EateryID.ONE_ZERO_FOUR_WEST - elif id == 7: - return EateryID.LIBE_CAFE - elif id == 8: - return EateryID.ATRIUM_CAFE - elif id == 1: - return EateryID.BEAR_NECESSITIES - elif id == 25: - return EateryID.BECKER_HOUSE - elif id == 10: - return EateryID.BIG_RED_BARN - elif id == 11: - return EateryID.BUS_STOP_BAGELS - elif id == 12: - return EateryID.CAFE_JENNIE - elif id == 26: - return EateryID.COOK_HOUSE - elif id == 14: - return EateryID.DAIRY_BAR - elif id == 41: - return EateryID.CROSSINGS_CAFE - elif id == 32: - return EateryID.FRANNYS - elif id == 16: - return EateryID.GOLDIES_CAFE - elif id == 15: - return EateryID.GREEN_DRAGON - elif id == 24: - return EateryID.HOT_DOG_CART - elif id == 34: - return EateryID.ICE_CREAM_BIKE - elif id == 27: - return EateryID.BETHE_HOUSE - elif id == 28: - return EateryID.JANSENS_MARKET - elif id == 29: - return EateryID.KEETON_HOUSE - elif id == 42: - return EateryID.MANN_CAFE - elif id == 18: - return EateryID.MARTHAS_CAFE - elif id == 19: - return EateryID.MATTINS_CAFE - elif id == 33: - return EateryID.MCCORMICKS - elif id == 3: - return EateryID.NORTH_STAR_DINING - elif id == 20: - return EateryID.OKENSHIELDS - elif id == 4: - return EateryID.RISLEY - elif id == 5: - return EateryID.RPCC - elif id == 30: - return EateryID.ROSE_HOUSE - elif id == 21: - return EateryID.RUSTYS - elif id == 13: - return EateryID.STRAIGHT_FROM_THE_MARKET - elif id == 23: - return EateryID.TRILLIUM - elif id == 43: - return EateryID.MORRISON_DINING - elif id == 44: - return EateryID.NOVICKS_CAFE - elif id == 45: - return EateryID.VET_CAFE - else: - print(f"Missing eatery_id {id}") - return None + """Convert dining ID to internal eatery ID""" + if id in DINING_ID_TO_INTERNAL: + return DINING_ID_TO_INTERNAL[id] + print(f"Missing eatery_id {id}") + return None -# Our transactions vendor def vendor_name_to_internal_id(vendor_eatery_name): + """Convert vendor eatery name to internal eatery ID""" + # Normalize vendor name: lowercase and remove non-alphabetic characters vendor_eatery_name = "".join(c.lower() for c in vendor_eatery_name if c.isalpha()) - if vendor_eatery_name == "bearnecessities": - return EateryID.BEAR_NECESSITIES - elif vendor_eatery_name == "northstarmarketplace": - return EateryID.NORTH_STAR_DINING - elif vendor_eatery_name == "jansensmarket": - return EateryID.JANSENS_MARKET - elif ( - vendor_eatery_name == "stockinghallcafe" or vendor_eatery_name == "stockinghall" - ): - return EateryID.DAIRY_BAR - elif vendor_eatery_name == "marthas": - return EateryID.MARTHAS_CAFE - elif vendor_eatery_name == "cafejennie": - return EateryID.CAFE_JENNIE - elif vendor_eatery_name == "goldiescafe": - return EateryID.GOLDIES_CAFE - elif vendor_eatery_name == "alicecookhouse": - return EateryID.COOK_HOUSE - elif vendor_eatery_name == "carlbeckerhouse": - return EateryID.BECKER_HOUSE - elif vendor_eatery_name == "duffield": - return EateryID.MATTINS_CAFE - elif vendor_eatery_name == "greendragon": - return EateryID.GREEN_DRAGON - elif vendor_eatery_name == "trillium": - return EateryID.TRILLIUM - elif vendor_eatery_name == "olinlibecafe": - return EateryID.LIBE_CAFE - elif vendor_eatery_name == "carolscafe": - return EateryID.CAROLS_CAFE - elif vendor_eatery_name == "statlerterrace": - return EateryID.TERRACE - elif vendor_eatery_name == "busstopbagels": - return EateryID.BUS_STOP_BAGELS - elif vendor_eatery_name == "kosher": - return EateryID.ONE_ZERO_FOUR_WEST - elif vendor_eatery_name == "jansensatbethehouse": - return EateryID.BETHE_HOUSE - elif vendor_eatery_name == "keetonhouse": - return EateryID.KEETON_HOUSE - elif vendor_eatery_name == "rpme": - return EateryID.RPCC - elif vendor_eatery_name == "rosehouse": - return EateryID.ROSE_HOUSE - elif vendor_eatery_name == "risley": - return EateryID.RISLEY - elif vendor_eatery_name == "frannysft": - return EateryID.FRANNYS - elif vendor_eatery_name == "mccormicks": - return EateryID.MCCORMICKS - elif vendor_eatery_name == "sage": - return EateryID.ATRIUM_CAFE - elif vendor_eatery_name == "straightmarket": - return EateryID.STRAIGHT_FROM_THE_MARKET - elif vendor_eatery_name == "crossingscafe": - return EateryID.CROSSINGS_CAFE - elif vendor_eatery_name == "okenshields": - return EateryID.OKENSHIELDS - elif vendor_eatery_name == "bigredbarn": - return EateryID.BIG_RED_BARN - elif vendor_eatery_name == "rustys": - return EateryID.RUSTYS - elif vendor_eatery_name == "manncafe": - return EateryID.MANN_CAFE - elif vendor_eatery_name == "statlermacs": - return EateryID.MACS_CAFE - elif vendor_eatery_name == "morrisondining" or vendor_eatery_name == "morrison": - return EateryID.MORRISON_DINING - elif vendor_eatery_name == "novickscafe": - return EateryID.NOVICKS_CAFE - elif vendor_eatery_name == "Vet College Cafe": - return EateryID.VET_CAFE - else: - # TODO: Add a slack notif / flag that a wait time location was not recognized - return None + + if vendor_eatery_name in VENDOR_NAME_TO_INTERNAL: + return VENDOR_NAME_TO_INTERNAL[vendor_eatery_name] + + # TODO: Add a slack notif / flag that a wait time location was not recognized + return None + def internal_id_to_image_url(id: EateryID): - if id == EateryID.ONE_ZERO_FOUR_WEST: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg" - elif id == EateryID.LIBE_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg" - elif id == EateryID.ATRIUM_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg" - elif id == EateryID.BEAR_NECESSITIES: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg" - elif id == EateryID.BECKER_HOUSE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg" - elif id == EateryID.BIG_RED_BARN: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg" - elif id == EateryID.BUS_STOP_BAGELS: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg" - elif id == EateryID.CAFE_JENNIE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg" - elif id == EateryID.COOK_HOUSE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg" - elif id == EateryID.DAIRY_BAR: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg" - elif id == EateryID.CROSSINGS_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg" - elif id == EateryID.FRANNYS: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg" - elif id == EateryID.GOLDIES_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg" - elif id == EateryID.GREEN_DRAGON: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg" - elif id == EateryID.HOT_DOG_CART: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg" - elif id == EateryID.ICE_CREAM_BIKE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg" - elif id == EateryID.BETHE_HOUSE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg" - elif id == EateryID.JANSENS_MARKET: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg" - elif id == EateryID.KEETON_HOUSE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg" - elif id == EateryID.MANN_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg" - elif id == EateryID.MARTHAS_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg" - elif id == EateryID.MATTINS_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg" - elif id == EateryID.MCCORMICKS: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg" - elif id == EateryID.NORTH_STAR_DINING: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg" - elif id == EateryID.OKENSHIELDS: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg" - elif id == EateryID.RISLEY: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg" - elif id == EateryID.ROSE_HOUSE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg" - elif id == EateryID.RUSTYS: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg" - elif id == EateryID.STRAIGHT_FROM_THE_MARKET: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg" - elif id == EateryID.TRILLIUM: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg" - elif id == EateryID.MORRISON_DINING: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg" - elif id == EateryID.NOVICKS_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/novicks-cafe.jpg" - elif id == EateryID.VET_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/vets-cafe.jpg" - elif id == EateryID.MACS_CAFE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Macs-Cafe.jpg" - elif id == EateryID.TERRACE: - return "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg" - else: - print(f"Missing image url for eatery_id {id}") - return "https://images-prod.healthline.com/hlcmsresource/images/AN_images/health-benefits-of-apples-1296x728-feature.jpg" \ No newline at end of file + """Convert internal eatery ID to image URL""" + if id in INTERNAL_ID_TO_IMAGE_URL: + return INTERNAL_ID_TO_IMAGE_URL[id] + + print(f"Missing image url for eatery_id {id}") + return DEFAULT_IMAGE_URL \ No newline at end of file diff --git a/src/static_sources/external_eateries.json b/src/static_sources/external_eateries.json index baafc30..e5201bc 100644 --- a/src/static_sources/external_eateries.json +++ b/src/static_sources/external_eateries.json @@ -938,56 +938,67 @@ ], "diningItems": [ { - "item": "Fat-Free Milk", + "item": "Pickles", "healthy": false, "category": "Free Food", "dietaryPreferences": [], "allergens": [ - "Dairy" + "N/A", + "" ] }, { - "item": "Chicken Caesar Wrap", + "item": "Oat Milk", "healthy": false, "category": "Free Food", - "dietaryPreferences": [], + "dietaryPreferences": [ + "Vegan" + ], "allergens": [ - "Dairy", - "Shellfish" + "Oats" ] }, { - "item": "Prosciutto & Mozzarella", + "item": "Chocolate Chip Cookie", "healthy": false, "category": "Free Food", - "dietaryPreferences": [], + "dietaryPreferences": [ + "Vegetarian", + "N/A" + ], "allergens": [ - "Soy", - "Nuts", - "Dairy", - "Shellfish" + "Dairy" ] }, { - "item": "Roast Beef & Asiago Cheese", + "item": "Pesto pasta", "healthy": false, "category": "Free Food", - "dietaryPreferences": [], - "allergens": [ - "Soy", - "Nuts", - "Dairy" - ] + "dietaryPreferences": [ + "Vegan" + ], + "allergens": [] }, { - "item": "Roasted Turkey & Swiss", + "item": "Rice, South Indian cuisine", "healthy": false, "category": "Free Food", - "dietaryPreferences": [], + "dietaryPreferences": [ + "Vegetarian" + ], + "allergens": [] + }, + { + "item": "Salad & condiments", + "healthy": false, + "category": "Free Food", + "dietaryPreferences": [ + "Vegetarian", + "Vegan" + ], "allergens": [ "Soy", - "Nuts", - "Dairy" + "Nuts" ] } ] From 5dabbbdf6ea52c5d29226508f6efff187e00f2e0 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 17 Apr 2025 15:34:46 -0400 Subject: [PATCH 303/305] one dictionary --- src/eatery/controllers/populate_eatery.py | 10 +- src/eatery/util/constants.py | 269 +++++++++++++++------- 2 files changed, 190 insertions(+), 89 deletions(-) diff --git a/src/eatery/controllers/populate_eatery.py b/src/eatery/controllers/populate_eatery.py index dce241a..13e365a 100644 --- a/src/eatery/controllers/populate_eatery.py +++ b/src/eatery/controllers/populate_eatery.py @@ -1,5 +1,5 @@ import json -from eatery.util.constants import dining_id_to_internal_id, internal_id_to_image_url, SnapshotFileName +from eatery.util.constants import dining_id_to_internal_id, dining_id_to_image_url, SnapshotFileName from eatery.serializers import EaterySerializer from eatery.models import Eatery from django.core.exceptions import ObjectDoesNotExist @@ -13,11 +13,11 @@ def generate_eatery(self, json_eatery): """ Create Eatery object from an eatery json from CornellDiningNow, and add to Eatery table. """ - eatery_id = dining_id_to_internal_id(json_eatery["id"]) + eatery_id = dining_id_to_internal_id(json_eatery["id"]).value data = { - "id": eatery_id.value, + "id": eatery_id, "name": json_eatery["name"], - "image_url": internal_id_to_image_url(eatery_id), + "image_url": dining_id_to_image_url(json_eatery["id"]), "campus_area": json_eatery["campusArea"]["descrshort"], "latitude": json_eatery["latitude"], "longitude": json_eatery["longitude"], @@ -38,7 +38,7 @@ def generate_eatery(self, json_eatery): "online_order_url": json_eatery["onlineOrderUrl"], } try: - object = Eatery.objects.get(id=int(eatery_id.value)) + object = Eatery.objects.get(id=int(eatery_id)) except ObjectDoesNotExist: """ Create a new Eatery object diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index 2b04c78..04cf25a 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -36,45 +36,147 @@ class SnapshotFileName(Enum): SCHEDULE_EXCEPTION = "schedule_exception.txt" -# Dictionary mapping dining IDs to internal IDs -DINING_ID_TO_INTERNAL = { - 31: EateryID.ONE_ZERO_FOUR_WEST, - 7: EateryID.LIBE_CAFE, - 8: EateryID.ATRIUM_CAFE, - 1: EateryID.BEAR_NECESSITIES, - 25: EateryID.BECKER_HOUSE, - 10: EateryID.BIG_RED_BARN, - 11: EateryID.BUS_STOP_BAGELS, - 12: EateryID.CAFE_JENNIE, - 26: EateryID.COOK_HOUSE, - 14: EateryID.DAIRY_BAR, - 41: EateryID.CROSSINGS_CAFE, - 32: EateryID.FRANNYS, - 16: EateryID.GOLDIES_CAFE, - 15: EateryID.GREEN_DRAGON, - 24: EateryID.HOT_DOG_CART, - 34: EateryID.ICE_CREAM_BIKE, - 27: EateryID.BETHE_HOUSE, - 28: EateryID.JANSENS_MARKET, - 29: EateryID.KEETON_HOUSE, - 42: EateryID.MANN_CAFE, - 18: EateryID.MARTHAS_CAFE, - 19: EateryID.MATTINS_CAFE, - 33: EateryID.MCCORMICKS, - 3: EateryID.NORTH_STAR_DINING, - 20: EateryID.OKENSHIELDS, - 4: EateryID.RISLEY, - 5: EateryID.RPCC, - 30: EateryID.ROSE_HOUSE, - 21: EateryID.RUSTYS, - 13: EateryID.STRAIGHT_FROM_THE_MARKET, - 23: EateryID.TRILLIUM, - 43: EateryID.MORRISON_DINING, - 44: EateryID.NOVICKS_CAFE, - 45: EateryID.VET_CAFE, +# Combined dictionary mapping dining IDs to both internal IDs and image URLs +EATERY_INFO = { + 31: { + "internal_id": EateryID.ONE_ZERO_FOUR_WEST, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg" + }, + 7: { + "internal_id": EateryID.LIBE_CAFE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg" + }, + 8: { + "internal_id": EateryID.ATRIUM_CAFE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg" + }, + 1: { + "internal_id": EateryID.BEAR_NECESSITIES, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg" + }, + 25: { + "internal_id": EateryID.BECKER_HOUSE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg" + }, + 10: { + "internal_id": EateryID.BIG_RED_BARN, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg" + }, + 11: { + "internal_id": EateryID.BUS_STOP_BAGELS, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg" + }, + 12: { + "internal_id": EateryID.CAFE_JENNIE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg" + }, + 26: { + "internal_id": EateryID.COOK_HOUSE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg" + }, + 14: { + "internal_id": EateryID.DAIRY_BAR, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg" + }, + 41: { + "internal_id": EateryID.CROSSINGS_CAFE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg" + }, + 32: { + "internal_id": EateryID.FRANNYS, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg" + }, + 16: { + "internal_id": EateryID.GOLDIES_CAFE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg" + }, + 15: { + "internal_id": EateryID.GREEN_DRAGON, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg" + }, + 24: { + "internal_id": EateryID.HOT_DOG_CART, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg" + }, + 34: { + "internal_id": EateryID.ICE_CREAM_BIKE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg" + }, + 27: { + "internal_id": EateryID.BETHE_HOUSE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg" + }, + 28: { + "internal_id": EateryID.JANSENS_MARKET, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg" + }, + 29: { + "internal_id": EateryID.KEETON_HOUSE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg" + }, + 42: { + "internal_id": EateryID.MANN_CAFE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg" + }, + 18: { + "internal_id": EateryID.MARTHAS_CAFE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg" + }, + 19: { + "internal_id": EateryID.MATTINS_CAFE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg" + }, + 33: { + "internal_id": EateryID.MCCORMICKS, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg" + }, + 3: { + "internal_id": EateryID.NORTH_STAR_DINING, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg" + }, + 20: { + "internal_id": EateryID.OKENSHIELDS, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg" + }, + 4: { + "internal_id": EateryID.RISLEY, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg" + }, + 5: { + "internal_id": EateryID.RPCC, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg" + }, + 30: { + "internal_id": EateryID.ROSE_HOUSE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg" + }, + 21: { + "internal_id": EateryID.RUSTYS, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg" + }, + 13: { + "internal_id": EateryID.STRAIGHT_FROM_THE_MARKET, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg" + }, + 23: { + "internal_id": EateryID.TRILLIUM, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg" + }, + 43: { + "internal_id": EateryID.MORRISON_DINING, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg" + }, + 44: { + "internal_id": EateryID.NOVICKS_CAFE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/novicks-cafe.jpg" + }, + 45: { + "internal_id": EateryID.VET_CAFE, + "image_url": "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/vets-cafe.jpg" + } } -# Dictionary mapping vendor names to internal IDs +# Dictionary mapping vendor names to internal IDs (keeping original) VENDOR_NAME_TO_INTERNAL = { "bearnecessities": EateryID.BEAR_NECESSITIES, "northstarmarketplace": EateryID.NORTH_STAR_DINING, @@ -114,52 +216,51 @@ class SnapshotFileName(Enum): "Vet College Cafe": EateryID.VET_CAFE, } -# Dictionary mapping internal IDs to image URLs -INTERNAL_ID_TO_IMAGE_URL = { - EateryID.ONE_ZERO_FOUR_WEST: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/104-West.jpg", - EateryID.LIBE_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Amit-Bhatia-Libe-Cafe.jpg", - EateryID.ATRIUM_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Atrium-Cafe.jpg", - EateryID.BEAR_NECESSITIES: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bear-Necessities.jpg", - EateryID.BECKER_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Becker-House-Dining.jpg", - EateryID.BIG_RED_BARN: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Big-Red-Barn.jpg", - EateryID.BUS_STOP_BAGELS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Bug-Stop-Bagels.jpg", - EateryID.CAFE_JENNIE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cafe-Jennie.jpg", - EateryID.COOK_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cook-House-Dining.jpg", - EateryID.DAIRY_BAR: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Cornell-Dairy-Bar.jpg", - EateryID.CROSSINGS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Crossings-Cafe.jpg", - EateryID.FRANNYS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/frannys.jpg", - EateryID.GOLDIES_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Goldies-Cafe.jpg", - EateryID.GREEN_DRAGON: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Green-Dragon.jpg", - EateryID.HOT_DOG_CART: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Hot-Dog-Cart.jpg", - EateryID.ICE_CREAM_BIKE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/icecreamcart.jpg", - EateryID.BETHE_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Dining.jpg", - EateryID.JANSENS_MARKET: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Jansens-Market.jpg", - EateryID.KEETON_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Keeton-House-Dining.jpg", - EateryID.MANN_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mann-Cafe.jpg", - EateryID.MARTHAS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Marthas-Cafe.jpg", - EateryID.MATTINS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Mattins-Cafe.jpg", - EateryID.MCCORMICKS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/mccormicks.jpg", - EateryID.NORTH_STAR_DINING: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/North-Star.jpg", - EateryID.OKENSHIELDS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Okenshields.jpg", - EateryID.RISLEY: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Risley-Dining.jpg", - EateryID.ROSE_HOUSE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rose-House-Dining.jpg", - EateryID.RUSTYS: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Rustys.jpg", - EateryID.STRAIGHT_FROM_THE_MARKET: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/StraightMarket.jpg", - EateryID.TRILLIUM: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Trillium.jpg", - EateryID.MORRISON_DINING: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Morrison-Dining.jpg", - EateryID.NOVICKS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/novicks-cafe.jpg", - EateryID.VET_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/vets-cafe.jpg", - EateryID.MACS_CAFE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Macs-Cafe.jpg", - EateryID.TERRACE: "https://raw.githubusercontent.com/cuappdev/assets/master/eatery/eatery-images/Terrace.jpg", -} + +def get_eatery_info(id: int): + """Get both internal ID and image URL for a given dining ID""" + if id in EATERY_INFO: + return EATERY_INFO[id] + + print(f"Missing eatery_id {id}") + return { + "internal_id": None, + "image_url": DEFAULT_IMAGE_URL + } + + +def get_eatery_info_by_vendor_name(vendor_eatery_name): + """Get both internal ID and image URL for a given vendor name""" + # Normalize vendor name: lowercase and remove non-alphabetic characters + vendor_eatery_name = "".join(c.lower() for c in vendor_eatery_name if c.isalpha()) + + # Get the internal ID from the vendor name + internal_id = VENDOR_NAME_TO_INTERNAL.get(vendor_eatery_name) + + if internal_id is not None: + # Find the dining ID that corresponds to this internal ID + for dining_id, info in EATERY_INFO.items(): + if info["internal_id"] == internal_id: + return info + + # If we can't find the eatery info, return the internal ID with default image + return { + "internal_id": internal_id, + "image_url": DEFAULT_IMAGE_URL + } + + # TODO: Add a slack notif / flag that a wait time location was not recognized + return { + "internal_id": None, + "image_url": DEFAULT_IMAGE_URL + } +# Legacy functions to maintain compatibility def dining_id_to_internal_id(id: int): """Convert dining ID to internal eatery ID""" - if id in DINING_ID_TO_INTERNAL: - return DINING_ID_TO_INTERNAL[id] - print(f"Missing eatery_id {id}") - return None + info = get_eatery_info(id) + return info["internal_id"] def vendor_name_to_internal_id(vendor_eatery_name): @@ -174,10 +275,10 @@ def vendor_name_to_internal_id(vendor_eatery_name): return None -def internal_id_to_image_url(id: EateryID): - """Convert internal eatery ID to image URL""" - if id in INTERNAL_ID_TO_IMAGE_URL: - return INTERNAL_ID_TO_IMAGE_URL[id] +def dining_id_to_image_url(id: int): + """Convert dining ID directly to image URL""" + if id in EATERY_INFO: + return EATERY_INFO[id]["image_url"] - print(f"Missing image url for eatery_id {id}") + print(f"Missing image url for dining_id {id}") return DEFAULT_IMAGE_URL \ No newline at end of file From 2aae72f3f57a85bf150d8eee5b73619b941acfbf Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 17 Apr 2025 15:36:00 -0400 Subject: [PATCH 304/305] remove some comments --- src/eatery/util/constants.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/eatery/util/constants.py b/src/eatery/util/constants.py index 04cf25a..f9494a2 100644 --- a/src/eatery/util/constants.py +++ b/src/eatery/util/constants.py @@ -36,7 +36,6 @@ class SnapshotFileName(Enum): SCHEDULE_EXCEPTION = "schedule_exception.txt" -# Combined dictionary mapping dining IDs to both internal IDs and image URLs EATERY_INFO = { 31: { "internal_id": EateryID.ONE_ZERO_FOUR_WEST, @@ -176,7 +175,6 @@ class SnapshotFileName(Enum): } } -# Dictionary mapping vendor names to internal IDs (keeping original) VENDOR_NAME_TO_INTERNAL = { "bearnecessities": EateryID.BEAR_NECESSITIES, "northstarmarketplace": EateryID.NORTH_STAR_DINING, @@ -231,19 +229,15 @@ def get_eatery_info(id: int): def get_eatery_info_by_vendor_name(vendor_eatery_name): """Get both internal ID and image URL for a given vendor name""" - # Normalize vendor name: lowercase and remove non-alphabetic characters vendor_eatery_name = "".join(c.lower() for c in vendor_eatery_name if c.isalpha()) - # Get the internal ID from the vendor name internal_id = VENDOR_NAME_TO_INTERNAL.get(vendor_eatery_name) if internal_id is not None: - # Find the dining ID that corresponds to this internal ID for dining_id, info in EATERY_INFO.items(): if info["internal_id"] == internal_id: return info - # If we can't find the eatery info, return the internal ID with default image return { "internal_id": internal_id, "image_url": DEFAULT_IMAGE_URL @@ -256,7 +250,6 @@ def get_eatery_info_by_vendor_name(vendor_eatery_name): } -# Legacy functions to maintain compatibility def dining_id_to_internal_id(id: int): """Convert dining ID to internal eatery ID""" info = get_eatery_info(id) @@ -265,7 +258,6 @@ def dining_id_to_internal_id(id: int): def vendor_name_to_internal_id(vendor_eatery_name): """Convert vendor eatery name to internal eatery ID""" - # Normalize vendor name: lowercase and remove non-alphabetic characters vendor_eatery_name = "".join(c.lower() for c in vendor_eatery_name if c.isalpha()) if vendor_eatery_name in VENDOR_NAME_TO_INTERNAL: From 1572c655000da28e5fac30c84813a42e7bff6812 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 17 Apr 2025 15:42:50 -0400 Subject: [PATCH 305/305] stop tracking external_eateries.json --- src/static_sources/external_eateries.json | 1007 --------------------- 1 file changed, 1007 deletions(-) delete mode 100644 src/static_sources/external_eateries.json diff --git a/src/static_sources/external_eateries.json b/src/static_sources/external_eateries.json deleted file mode 100644 index e5201bc..0000000 --- a/src/static_sources/external_eateries.json +++ /dev/null @@ -1,1007 +0,0 @@ -{ - "eateries": [ - { - "id": 33, - "slug": "Terrace", - "external": true, - "name": "Terrace Restaurant", - "nameshort": "Terrace", - "about": "", - "contactPhone": "1-800-541-2501", - "latitude": 42.446267, - "longitude": -76.482314, - "location": "Statler", - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "10:00", - "end": "15:00", - "menu": [] - } - ] - } - ], - "datesClosed": [ - "9/6/21", - "11/25/21", - "11/26/21", - "11/27/21", - "11/28/21", - "12/20/21-1/20/22" - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - }, - { - "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", - "descrshort": "Meal Plan - Debit" - } - ], - "diningItems": [ - { - "item": "Salads", - "healthy": true, - "category": "General" - }, - { - "item": "Burrito Bowl", - "healthy": false, - "category": "General" - }, - { - "item": "Burrito", - "healthy": true, - "category": "General" - }, - { - "item": "Chicken Tenders", - "healthy": false, - "category": "General" - }, - { - "item": "Fries", - "healthy": false, - "category": "General" - }, - { - "item": "Pho", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 34, - "slug": "Macs", - "external": true, - "name": "Mac's Caf\u00e9", - "nameshort": "Mac's", - "about": "", - "cornellDining": false, - "contactPhone": "1-800-541-2501", - "latitude": 42.445921, - "longitude": -76.481984, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Statler Hotel", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "09:30", - "end": "17:30", - "menu": [] - } - ] - } - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - }, - { - "descr": "Meal Plan - Debit Plans (Big Red Bucks, Meal Choice, etc)", - "descrshort": "Meal Plan - Debit" - } - ], - "datesClosed": [ - "8/18/21", - "8/19/21", - "8/20/21", - "8/21/21", - "8/22/21", - "8/23/21", - "9/6/21", - "10/9/21", - "10/10/21", - "10/11/21", - "10/12/21", - "11/24/21", - "11/25/21", - "11/26/21", - "11/27/21", - "11/28/21", - "12/16/21", - "12/17/21", - "12/20/21-1/20/22" - ], - "diningItems": [ - { - "item": "Pizza", - "healthy": false, - "category": "General" - }, - { - "item": "Pasta", - "healthy": false, - "category": "General" - }, - { - "item": "Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Sushi to Go", - "healthy": false, - "category": "General" - }, - { - "item": "Soft Drinks", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 35, - "slug": "Zeus", - "external": true, - "name": "Temple of Zeus", - "nameshort": "Temple of Zeus", - "about": "", - "cornellDining": false, - "contactPhone": "", - "latitude": 42.449091, - "longitude": -76.483414, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Goldwin Smith Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "17:00", - "menu": [] - } - ] - } - ], - "datesClosed": [ - "09/06/21" - ], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Sandwiches", - "healthy": true, - "category": "General" - }, - { - "item": "Soups", - "healthy": true, - "category": "General" - }, - { - "item": "Baked Goods", - "healthy": true, - "category": "General" - }, - { - "item": "Candy", - "healthy": false, - "category": "General" - }, - { - "item": "Soft Drinks", - "healthy": true, - "category": "General" - } - ] - }, - { - "id": 36, - "slug": "Gimme-Coffee", - "external": true, - "name": "Gimme Coffee", - "nameshort": "Gimme Coffee", - "about": "", - "cornellDining": false, - "contactPhone": "1-607-227-5391", - "latitude": 42.444958, - "longitude": -76.481169, - "campusArea": { - "descr": "Central Campus", - "descrshort": "Central" - }, - "location": "Gates Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "08:00", - "end": "15:00", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Coffee", - "healthy": true, - "category": "General" - }, - { - "item": "Baked Goods", - "healthy": true, - "category": "General" - } - ] - }, - { - "id": 37, - "slug": "Louies-Lunch", - "external": true, - "name": "Louie's Lunch", - "nameshort": "Louie's", - "about": "", - "cornellDining": false, - "contactPhone": "1-607-257-4649", - "latitude": 42.45336, - "longitude": -76.481225, - "campusArea": { - "descr": "North Campus", - "descrshort": "North" - }, - "location": "Across from Risley", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "11:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Saturday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "03:00", - "menu": [] - } - ] - }, - { - "weekday": "Sunday", - "events": [ - { - "descr": "General", - "start": "18:00", - "end": "00:00", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "French Fries", - "healthy": false, - "category": "General" - }, - { - "item": "Garlic Bread", - "healthy": false, - "category": "General" - }, - { - "item": "Cold Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Subs", - "healthy": false, - "category": "General" - }, - { - "item": "Pizza Subs", - "healthy": false, - "category": "General" - }, - { - "item": "Burgers", - "healthy": false, - "category": "General" - }, - { - "item": "Egg Sandwiches", - "healthy": false, - "category": "General" - }, - { - "item": "Hot Dogs", - "healthy": false, - "category": "General" - }, - { - "item": "Wraps", - "healthy": false, - "category": "General" - }, - { - "item": "Salads", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 38, - "slug": "Anabels-Grocery", - "external": true, - "name": "Anabel's Grocery", - "nameshort": "Anabel's", - "about": "", - "cornellDining": false, - "contactPhone": "", - "latitude": 42.445061, - "longitude": -76.485826, - "campusArea": { - "descr": "South Campus", - "descrshort": "South" - }, - "location": "Anabel Taylor Hall", - "eateryTypes": [ - { - "descr": "Cafe", - "descrshort": "cafe" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Wednesday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "19:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "19:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "19:00", - "menu": [] - } - ] - }, - { - "weekday": "Saturday", - "events": [ - { - "descr": "General", - "start": "12:00", - "end": "15:00", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Cash", - "descrshort": "Cash" - }, - { - "descr": "Cornell Card", - "descrshort": "Cornell Card" - }, - { - "descr": "Major Credit Cards (VISA, MasterCard, AMEX)", - "descrshort": "Major Credit Cards" - } - ], - "diningItems": [ - { - "item": "Whole Grains", - "healthy": true, - "category": "General" - }, - { - "item": "Spices", - "healthy": false, - "category": "General" - }, - { - "item": "Legumes", - "healthy": true, - "category": "General" - }, - { - "item": "Fresh and Frozen Produce", - "healthy": true, - "category": "General" - }, - { - "item": "Nuts", - "healthy": true, - "category": "General" - }, - { - "item": "Dried Fruits", - "healthy": true, - "category": "General" - }, - { - "item": "Tofu", - "healthy": true, - "category": "General" - }, - { - "item": "Eggs", - "healthy": false, - "category": "General" - }, - { - "item": "Milks", - "healthy": true, - "category": "General" - }, - { - "item": "Kombucha on Tap", - "healthy": true, - "category": "General" - }, - { - "item": "Bottled Drinks", - "healthy": false, - "category": "General" - }, - { - "item": "Bulk Items", - "healthy": false, - "category": "General" - } - ] - }, - { - "id": 46, - "slug": "Freedge", - "external": true, - "name": "Free Food Fridge", - "nameshort": "Freedge", - "about": "", - "cornellDining": false, - "contactPhone": "", - "latitude": 42.445061, - "longitude": -76.485826, - "campusArea": { - "descr": "South Campus", - "descrshort": "South" - }, - "location": "Anabel Taylor Hall Room 120", - "eateryTypes": [ - { - "descr": "Cash", - "descrshort": "Cash" - } - ], - "onlineOrdering": false, - "onlineOrderUrl": null, - "operatingHours": [ - { - "weekday": "Monday", - "events": [ - { - "descr": "Free Food", - "start": "08:30", - "end": "23:00", - "menu": [] - } - ] - }, - { - "weekday": "Tuesday", - "events": [ - { - "descr": "Free Food", - "start": "08:30", - "end": "23:00", - "menu": [] - } - ] - }, - { - "weekday": "Wednesday", - "events": [ - { - "descr": "Free Food", - "start": "08:30", - "end": "23:00", - "menu": [] - } - ] - }, - { - "weekday": "Thursday", - "events": [ - { - "descr": "Free Food", - "start": "08:30", - "end": "23:00", - "menu": [] - } - ] - }, - { - "weekday": "Friday", - "events": [ - { - "descr": "Free Food", - "start": "08:30", - "end": "23:00", - "menu": [] - } - ] - }, - { - "weekday": "Saturday", - "events": [ - { - "descr": "Free Food", - "start": "08:30", - "end": "23:00", - "menu": [] - } - ] - }, - { - "weekday": "Sunday", - "events": [ - { - "descr": "Free Food", - "start": "08:30", - "end": "23:00", - "menu": [] - } - ] - } - ], - "datesClosed": [], - "payMethods": [ - { - "descr": "Free", - "descrshort": "Free" - } - ], - "diningItems": [ - { - "item": "Pickles", - "healthy": false, - "category": "Free Food", - "dietaryPreferences": [], - "allergens": [ - "N/A", - "" - ] - }, - { - "item": "Oat Milk", - "healthy": false, - "category": "Free Food", - "dietaryPreferences": [ - "Vegan" - ], - "allergens": [ - "Oats" - ] - }, - { - "item": "Chocolate Chip Cookie", - "healthy": false, - "category": "Free Food", - "dietaryPreferences": [ - "Vegetarian", - "N/A" - ], - "allergens": [ - "Dairy" - ] - }, - { - "item": "Pesto pasta", - "healthy": false, - "category": "Free Food", - "dietaryPreferences": [ - "Vegan" - ], - "allergens": [] - }, - { - "item": "Rice, South Indian cuisine", - "healthy": false, - "category": "Free Food", - "dietaryPreferences": [ - "Vegetarian" - ], - "allergens": [] - }, - { - "item": "Salad & condiments", - "healthy": false, - "category": "Free Food", - "dietaryPreferences": [ - "Vegetarian", - "Vegan" - ], - "allergens": [ - "Soy", - "Nuts" - ] - } - ] - } - ] -} \ No newline at end of file