Skip to content

Commit

Permalink
Refactor dining + add nutrition info (#299)
Browse files Browse the repository at this point in the history
  • Loading branch information
vcai122 authored May 7, 2024
1 parent 909f571 commit ad9360f
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 44 deletions.
43 changes: 25 additions & 18 deletions backend/dining/api_wrapper.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import json

import requests
from django.conf import settings
Expand Down Expand Up @@ -118,11 +119,8 @@ def load_menu(self, date=timezone.now().date()):

# TODO: Handle API responses during empty menus (holidays)
menu_base = OPEN_DATA_ENDPOINTS["MENUS"]
venues = Venue.objects.all()
venues = [v for v in Venue.objects.all() if v.venue_id not in skipped_venues]
for venue in venues:
if venue.venue_id in skipped_venues:
continue

response = self.request("GET", f"{menu_base}?cafe={venue.venue_id}&date={date}").json()
# Load new items into database
# TODO: There is something called a "goitem" for venues like English House.
Expand All @@ -147,33 +145,42 @@ def load_menu(self, date=timezone.now().date()):
service=daypart["label"],
)
# Append stations to dining menu
stations = self.load_stations(daypart["stations"])
dining_menu.stations.add(*stations)
dining_menu.save()
self.load_stations(daypart["stations"], dining_menu)

def load_stations(self, station_response):
stations = [None] * len(station_response)
for i, station_data in enumerate(station_response):
def load_stations(self, station_response, dining_menu):
for station_data in station_response:
# TODO: This is inefficient for venues such as Houston Market
station = DiningStation.objects.create(name=station_data["label"])
station = DiningStation.objects.create(name=station_data["label"], menu=dining_menu)
item_ids = [int(item) for item in station_data["items"]]

# Bulk add the items into the station
items = DiningItem.objects.filter(item_id__in=item_ids)
station.items.add(*items)
station.save()
stations[i] = station
return stations

def load_items(self, item_response):
item_list = [None] * len(item_response)
for i, key in enumerate(item_response):
value = item_response[key]
item_list[i] = DiningItem(
item_list = [
DiningItem(
item_id=key,
name=value["label"],
description=value["description"],
ingredients=value["ingredients"],
allergens=", ".join(value["cor_icon"].values()) if value["cor_icon"] else "",
nutrition_info=json.dumps(
{
x["label"]: f"{x['value']}{x['unit']}"
for x in value["nutrition_details"].values()
}
),
)
for key, value in item_response.items()
]
# Ignore conflicts because possibility of duplicate items
DiningItem.objects.bulk_create(item_list, ignore_conflicts=True)
DiningItem.objects.bulk_create(
item_list,
update_conflicts=True,
update_fields=[
field.name for field in DiningItem._meta.fields if not field.primary_key
],
unique_fields=[DiningItem._meta.pk.name],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 5.0.2 on 2024-05-06 22:00

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("dining", "0005_diningitem_allergens_diningitem_nutrition_info_and_more"),
]

operations = [
migrations.RemoveField(
model_name="diningmenu",
name="stations",
),
migrations.AlterField(
model_name="diningitem",
name="allergens",
field=models.CharField(blank=True, max_length=1000),
),
migrations.AlterField(
model_name="diningitem",
name="description",
field=models.CharField(blank=True, max_length=1000),
),
migrations.AlterField(
model_name="diningitem",
name="ingredients",
field=models.CharField(blank=True, max_length=1000),
),
migrations.AlterField(
model_name="diningitem",
name="nutrition_info",
field=models.CharField(blank=True, max_length=1000),
),
migrations.AlterField(
model_name="diningstation",
name="menu",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="stations",
to="dining.diningmenu",
),
),
]
11 changes: 5 additions & 6 deletions backend/dining/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ def __str__(self):
class DiningItem(models.Model):
item_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255)
description = models.CharField(max_length=1000)
ingredients = models.CharField(max_length=1000) # comma separated list
allergens = models.CharField(max_length=1000) # comma separated list
nutrition_info = models.CharField(max_length=1000) # json string.
description = models.CharField(max_length=1000, blank=True)
ingredients = models.CharField(max_length=1000, blank=True) # comma separated list
allergens = models.CharField(max_length=1000, blank=True) # comma separated list
nutrition_info = models.CharField(max_length=1000, blank=True) # json string.
# Technically, postgres supports json fields but that involves local postgres
# instead of sqlite AND we don't need to query on this field

Expand All @@ -28,13 +28,12 @@ def __str__(self):
class DiningStation(models.Model):
name = models.CharField(max_length=255)
items = models.ManyToManyField(DiningItem)
menu = models.ForeignKey("DiningMenu", on_delete=models.CASCADE)
menu = models.ForeignKey("DiningMenu", on_delete=models.CASCADE, related_name="stations")


class DiningMenu(models.Model):
venue = models.ForeignKey(Venue, on_delete=models.CASCADE)
date = models.DateField(default=timezone.now)
start_time = models.DateTimeField()
end_time = models.DateTimeField()
stations = models.ManyToManyField(DiningStation)
service = models.CharField(max_length=255)
6 changes: 3 additions & 3 deletions backend/dining/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
class VenueSerializer(serializers.ModelSerializer):
class Meta:
model = Venue
fields = ("venue_id", "name", "image_url")
fields = "__all__"


class DiningItemSerializer(serializers.ModelSerializer):
class Meta:
model = DiningItem
fields = ("item_id", "name", "description", "ingredients")
fields = "__all__"


class DiningStationSerializer(serializers.ModelSerializer):
Expand All @@ -29,4 +29,4 @@ class DiningMenuSerializer(serializers.ModelSerializer):

class Meta:
model = DiningMenu
fields = ("venue", "date", "start_time", "end_time", "stations", "service")
fields = "__all__"
35 changes: 18 additions & 17 deletions backend/tests/dining/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from rest_framework.test import APIClient

from dining.api_wrapper import APIError, DiningAPIWrapper
from dining.models import Venue
from dining.models import DiningMenu, Venue


User = get_user_model()
Expand Down Expand Up @@ -128,23 +128,24 @@ def try_structure(self, data):
self.assertIn("name", item)
self.assertIn("description", item)
self.assertIn("ingredients", item)
self.assertIn("allergens", item)
self.assertIn("nutrition_info", item)

# COMMEND OUT FOR MIGRATION
# def test_get_default(self):
# response = self.client.get(reverse("menus"))
# self.try_structure(response.json())

# def test_get_date(self):
# response = self.client.get("/dining/menus/2022-10-04/")
# self.try_structure(response.json())

# @mock.patch("requests.request", mock_dining_requests)
# def test_skip_venue(self):
# Venue.objects.all().delete()
# Venue.objects.create(venue_id=747, name="Skip", image_url="URL")
# wrapper = DiningAPIWrapper()
# wrapper.load_menu()
# self.assertEqual(DiningMenu.objects.count(), 0)
def test_get_default(self):
response = self.client.get(reverse("menus"))
self.try_structure(response.json())

def test_get_date(self):
response = self.client.get("/dining/menus/2022-10-04/")
self.try_structure(response.json())

@mock.patch("requests.request", mock_dining_requests)
def test_skip_venue(self):
Venue.objects.all().delete()
Venue.objects.create(venue_id=747, name="Skip", image_url="URL")
wrapper = DiningAPIWrapper()
wrapper.load_menu()
self.assertEqual(DiningMenu.objects.count(), 0)


class TestPreferences(TestCase):
Expand Down

0 comments on commit ad9360f

Please sign in to comment.