Skip to content

Commit 96d8df9

Browse files
authored
Merge pull request #108 from cuappdev/master
2 parents f0e01b0 + 7585f4a commit 96d8df9

9 files changed

Lines changed: 137 additions & 34 deletions

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ coreapi==2.3.3
88
coreschema==0.0.4
99
distlib==0.3.8
1010
Django==4.0
11+
django-db-connection-pool==1.2.5
1112
django-rest-swagger==2.2.0
1213
djangorestframework==3.13.1
1314
drf-yasg==1.21.7

src/category/views.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@
66
class CategoryViewSet(viewsets.ModelViewSet):
77
queryset = Category.objects.all()
88
serializer_class = CategorySerializer
9+
10+
def get_queryset(self):
11+
# prefetch items and their related fields
12+
return Category.objects.select_related('event__eatery').prefetch_related(
13+
'items__dietary_preferences',
14+
'items__allergens'
15+
).all()

src/eatery/views.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ class EateryViewSet(viewsets.ModelViewSet):
2727
serializer_class = EaterySerializer
2828
permission_classes = [EateryPermission]
2929

30+
def get_queryset(self):
31+
"""
32+
Override to add prefetch_related for optimization
33+
"""
34+
queryset = super().get_queryset()
35+
36+
# prefetch all related objects to avoid N+1 query problem
37+
return queryset.prefetch_related(
38+
'events__menu__items__dietary_preferences',
39+
'events__menu__items__allergens'
40+
)
41+
3042
def retrieve(self, request, *args, **kwargs):
3143
instance = self.get_object()
3244
serializer = EaterySerializerOptimized(instance)
@@ -98,7 +110,10 @@ class GetEateriesSimple(APIView):
98110
"""
99111

100112
def get(self, request):
101-
eateries = EaterySerializerSimple(Eatery.objects.all(), many=True)
113+
eateries_queryset = Eatery.objects.prefetch_related(
114+
'events'
115+
).all()
116+
eateries = EaterySerializerSimple(eateries_queryset, many=True)
102117
return Response(eateries.data)
103118

104119

@@ -109,9 +124,14 @@ class GetEateriesByDay(APIView):
109124

110125
@method_decorator(cache_page(60 * 60 * 2)) # cache for 2 hours
111126
def get(self, request, day):
127+
eateries_queryset = Eatery.objects.prefetch_related(
128+
'events__menu__items__dietary_preferences',
129+
'events__menu__items__allergens'
130+
).exclude(events__event_description="Open")
131+
112132
eateries = EaterySerializerByDay(
113-
Eatery.objects.exclude(events__event_description="Open"),
133+
eateries_queryset,
114134
many=True,
115135
context={"day": day},
116136
)
117-
return Response(eateries.data)
137+
return Response(eateries.data)

src/eatery_blue_backend/management/commands/populate_models.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from category.controllers.populate_category import PopulateCategoryController
1111
import os
1212
import json
13+
import shutil
1314

1415

1516
class Command(BaseCommand):
@@ -33,18 +34,36 @@ def get_json(self):
3334
json_eateries = response["data"]["eateries"]
3435
return json_eateries
3536

37+
def ensure_external_eateries_exists(self):
38+
external_path = "./static_sources/external_eateries.json"
39+
static_path = "./static_sources/external_eateries_static.json"
40+
41+
# if external_eateries.json doesn't exist, copy from static
42+
if not os.path.exists(external_path):
43+
if os.path.exists(static_path):
44+
shutil.copy2(static_path, external_path)
45+
print("Created external_eateries.json from static template")
46+
else:
47+
# create fallback file
48+
data = {"eateries": []}
49+
with open(external_path, "w") as f:
50+
json.dump(data, f, indent=2)
51+
print("Created minimal external_eateries.json")
52+
3653
def update_freedge_external_eatery(self):
54+
self.ensure_external_eateries_exists()
55+
3756
GOOGLE_SHEETS_API_KEY = os.environ.get("GOOGLE_SHEETS_API_KEY")
3857
FREEDGE_SHEET_ID = os.environ.get("FREEDGE_SHEET_ID")
3958
FREEDGE_APPROVED_EMAILS = os.environ.get("FREEDGE_APPROVED_EMAILS")
4059
if not GOOGLE_SHEETS_API_KEY:
41-
print("GOOGLE_SHEETS_API_KEY not set, cannot update freedge external eatery")
60+
print("GOOGLE_SHEETS_API_KEY not set, skipping freedge update")
4261
return
4362
if not FREEDGE_SHEET_ID:
44-
print("FREEDGE_SHEET_ID not set, cannot update freedge external eatery")
63+
print("FREEDGE_SHEET_ID not set, skipping freedge update")
4564
return
4665
if not FREEDGE_APPROVED_EMAILS:
47-
print("FREEDGE_APPROVED_EMAILS not set, cannot update freedge external eatery")
66+
print("FREEDGE_APPROVED_EMAILS not set, skipping freedge update")
4867
return
4968

5069
approved_emails = FREEDGE_APPROVED_EMAILS.split(",")

src/eatery_blue_backend/settings.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,20 @@
102102

103103
DATABASES = {
104104
"default": {
105-
"ENGINE": "django.db.backends.postgresql_psycopg2",
105+
"ENGINE": "dj_db_conn_pool.backends.postgresql",
106106
"NAME": os.getenv("POSTGRES_NAME"),
107107
"USER": os.getenv("POSTGRES_USER"),
108108
"PASSWORD": os.getenv("POSTGRES_PASSWORD"),
109109
"HOST": os.getenv("POSTGRES_HOST"),
110110
"PORT": os.getenv("POSTGRES_PORT"),
111-
'CONN_MAX_AGE': 0,
111+
"POOL_OPTIONS": {
112+
"POOL_SIZE": 10,
113+
"MAX_OVERFLOW": 10,
114+
"RECYCLE": 300,
115+
"PRE_PING": True,
116+
"ECHO": False,
117+
"TIMEOUT": 30,
118+
}
112119
}
113120
}
114121

src/event/views.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,10 @@
66
class EventViewSet(viewsets.ModelViewSet):
77
queryset = Event.objects.all()
88
serializer_class = EventSerializer
9+
10+
def get_queryset(self):
11+
# prefetch related menu categories and items
12+
return Event.objects.select_related('eatery').prefetch_related(
13+
'menu__items__dietary_preferences',
14+
'menu__items__allergens'
15+
).all()

src/item/controllers/populate_item.py

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from item.serializers import ItemSerializer
1+
from item.models import Item, DietaryPreference, Allergen
22
import json
33
from util.constants import eatery_is_cafe
44

@@ -7,6 +7,30 @@ class PopulateItemController:
77
def __init__(self):
88
self = self
99

10+
def create_item_with_m2m(self, category_id, name, dietary_preferences=None, allergens=None):
11+
# handle many to many relationships
12+
item, created = Item.objects.get_or_create(
13+
category_id=category_id,
14+
name=name,
15+
defaults={'base_price': 0.0}
16+
)
17+
18+
# handle dietary preferences
19+
if dietary_preferences:
20+
for pref_name in dietary_preferences:
21+
if pref_name:
22+
pref, _ = DietaryPreference.objects.get_or_create(name=pref_name)
23+
item.dietary_preferences.add(pref)
24+
25+
# handle allergens
26+
if allergens:
27+
for allergen_name in allergens:
28+
if allergen_name:
29+
allergen, _ = Allergen.objects.get_or_create(name=allergen_name)
30+
item.allergens.add(allergen)
31+
32+
return item
33+
1034
def generate_cafe_items(self, menu, json_eatery):
1135
for json_item in json_eatery["diningItems"]:
1236
category_name = json_item["category"].strip()
@@ -18,33 +42,32 @@ def generate_cafe_items(self, menu, json_eatery):
1842
dietary_preferences = json_item.get("dietaryPreferences", [])
1943
allergens = json_item.get("allergens", [])
2044

21-
data = {"category": category_id, "name": json_item["item"], "dietary_preferences": dietary_preferences, "allergens": allergens}
22-
try:
23-
item = ItemSerializer(data=data)
24-
if item.is_valid():
25-
item.save()
26-
else:
27-
print(item.errors)
28-
except Exception as e:
29-
print(f"Error saving item: {e}")
30-
print(f"Data: {data}")
31-
45+
self.create_item_with_m2m(
46+
category_id=category_id,
47+
name=json_item["item"],
48+
dietary_preferences=dietary_preferences,
49+
allergens=allergens
50+
)
3251

3352
def generate_dining_hall_items(self, menu, json_event, json_eatery):
3453
json_menus = json_event["menu"]
3554
for json_menu in json_menus:
3655
category_name = json_menu["category"].strip()
37-
category_id = menu[category_name]
56+
try:
57+
category_id = menu[category_name]
58+
except KeyError:
59+
continue
3860

3961
for json_item in json_menu["items"]:
4062
dietary_preferences = json_item.get("dietaryPreferences", [])
4163
allergens = json_item.get("allergens", [])
42-
data = {"category": category_id, "name": json_item["item"], "dietary_preferences": dietary_preferences, "allergens": allergens}
43-
item = ItemSerializer(data=data)
44-
if item.is_valid():
45-
item.save()
46-
else:
47-
print(item.errors)
64+
65+
self.create_item_with_m2m(
66+
category_id=category_id,
67+
name=json_item["item"],
68+
dietary_preferences=dietary_preferences,
69+
allergens=allergens
70+
)
4871

4972
def process(self, categories_dict, json_eateries):
5073
with open(

src/item/serializers.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
class ItemSerializer(serializers.ModelSerializer):
66
id = serializers.IntegerField(read_only=True)
77
name = serializers.CharField(default="Item")
8-
dietary_preferences = serializers.ListField(
9-
child=serializers.CharField(), allow_empty=True, default=[]
10-
)
11-
allergens = serializers.ListField(
12-
child=serializers.CharField(), allow_empty=True, default=[]
13-
)
8+
dietary_preferences = serializers.SerializerMethodField()
9+
allergens = serializers.SerializerMethodField()
10+
11+
def get_dietary_preferences(self, obj):
12+
"""Get list of dietary preference names"""
13+
return list(obj.dietary_preferences.values_list('name', flat=True))
14+
15+
def get_allergens(self, obj):
16+
"""Get list of allergen names"""
17+
return list(obj.allergens.values_list('name', flat=True))
1418

1519
def create(self, validated_data):
1620
dietary_prefs = validated_data.pop('dietary_preferences', [])
@@ -34,10 +38,17 @@ def create(self, validated_data):
3438
item.allergens.add(*allergens_objects)
3539

3640
return item
41+
42+
def update(self, instance, validated_data):
43+
instance.name = validated_data.get('name', instance.name)
44+
instance.base_price = validated_data.get('base_price', instance.base_price)
45+
instance.save()
46+
return instance
3747

3848
class Meta:
3949
model = Item
40-
fields = ["id", "category", "name", "dietary_preferences", "allergens"]
50+
fields = ["id", "category", "name", "base_price", "dietary_preferences", "allergens"]
51+
read_only_fields = ["id"]
4152

4253

4354
class ItemSerializerOptimized(serializers.ModelSerializer):

src/item/views.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,11 @@
66
class ItemViewSet(viewsets.ModelViewSet):
77
queryset = Item.objects.all()
88
serializer_class = ItemSerializer
9+
10+
def get_queryset(self):
11+
return Item.objects.select_related(
12+
'category__event__eatery'
13+
).prefetch_related(
14+
'dietary_preferences',
15+
'allergens'
16+
).all()

0 commit comments

Comments
 (0)