Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions deliveries/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,15 @@ def ready_orders(request):
completed=False,
order__picked_up=False,
timestamp__date=today
)
).select_related('order')
if courier:
for d in Delivery.objects.filter(completed = False).all():
if d.courier == request.user:
return redirect("delivery_order", id=d.id)
# Use a single optimized query with select_related to check for user's incomplete delivery
user_delivery = Delivery.objects.filter(
completed=False,
courier=request.user
).select_related('order').first()
if user_delivery:
return redirect("delivery_order", id=user_delivery.id)
return render(request, "deliveries/orders.html", {
"route":"orders",
"orders":orders,
Expand Down Expand Up @@ -143,10 +147,8 @@ def profile(request):
working = check_if_active_courier(request) != None
delivering = False
if working:
for d in Delivery.objects.filter(completed = False).all():
if d.courier == request.user:
delivering = True
break
# Use exists() for a more efficient check instead of iterating all deliveries
delivering = Delivery.objects.filter(completed=False, courier=request.user).exists()
return render(request, "deliveries/profile.html", {
"working":working,
"delivering":delivering,
Expand Down
5 changes: 4 additions & 1 deletion inventory/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,10 @@ def craft_component(component_id:int, qty:int):
- Decreases the inventory of each ingredient used in the component by the required amount,
unless the ingredient has unlimited supply.
"""
component = Component.objects.get(pk=component_id)
# Prefetch componentingredient_set with ingredient to avoid N+1 queries
component = Component.objects.prefetch_related(
'componentingredient_set__ingredient'
).get(pk=component_id)
component.inventory += qty
component.save()
for ci in component.componentingredient_set.all():
Expand Down
25 changes: 23 additions & 2 deletions online_store/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
from django.conf import settings
from gift_cards.models import GiftCard, GiftCardAuthorization
from django.utils.translation import gettext_lazy as _
import logging

logger = logging.getLogger(__name__)

def menu(request):
"""
Expand Down Expand Up @@ -97,7 +100,13 @@ def dish(request, id):
allergens, and serialized dish data in JSON format.
"""
try:
item = Dish.objects.get(pk=id)
# Prefetch related data to avoid N+1 queries
item = Dish.objects.prefetch_related(
'dishcomponent_set__component__componentingredient_set__ingredient',
'dishcomponent_set__component__child_dishes',
'components__child_dishes',
'menu'
).get(pk=id)
except Dish.DoesNotExist:
return HttpResponseNotFound(_("Dish not found"))
allergens = set()
Expand Down Expand Up @@ -237,8 +246,20 @@ def place_order(request):
card.charge_card(payment["amount"])
GiftCardAuthorization.objects.create(card=card, order=order, charged_balance=payment["amount"])
dish_counts = Counter(dish_ids)

# Fetch all dishes at once with prefetched data to avoid N+1 queries
unique_dish_ids = list(dish_counts.keys())
dishes_queryset = Dish.objects.filter(id__in=unique_dish_ids).prefetch_related(
'components__child_dishes',
'dishcomponent_set__component'
)
dishes_map = {dish.id: dish for dish in dishes_queryset}

for dish_id, quantity in dish_counts.items():
dish = Dish.objects.get(id=dish_id)
dish = dishes_map.get(dish_id)
if not dish:
logger.warning(f"Dish with id {dish_id} not found while processing online order")
continue
if check_if_only_choice_dish(dish):
continue
if dish.station == "bar":
Expand Down
24 changes: 18 additions & 6 deletions pos_server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ def check_if_only_choice_dish(self):
Returns:
bool: True if all components of the dish have child dishes, False otherwise.
"""
if not self.components.all():
# Use prefetch_related to avoid N+1 queries if not already prefetched
components = self.components.prefetch_related('child_dishes').all()
if not components:
return False
for component in self.components.all():
for component in components:
if not component.child_dishes.all():
return False
return True
Expand Down Expand Up @@ -123,23 +125,33 @@ def serialize_with_options(self):
- pk (int): The primary key of the dish (same as the ID).
"""
choice_components = []
for component in self.components.all():
if component.child_dishes.all():
# Prefetch components and child_dishes to avoid N+1 queries
components = self.components.prefetch_related('child_dishes').all()
all_have_children = True
has_components = False
for component in components:
has_components = True
child_dishes = list(component.child_dishes.all())
if child_dishes:
choices = {
"parent":{
"title":component.title,
"id":component.id
},
"children":[]
}
for child in component.child_dishes.all():
for child in child_dishes:
choices["children"].append({
"title":child.title,
"id":child.id,
"in_stock":child.in_stock,
"force_in_stock":child.force_in_stock
})
choice_components.append(choices)
else:
all_have_children = False
# Calculate only_choices inline to avoid redundant query
only_choices = has_components and all_have_children
return {
"fields":{
"title":self.title,
Expand All @@ -151,7 +163,7 @@ def serialize_with_options(self):
"in_stock":self.in_stock,
"force_in_stock":self.force_in_stock,
"choice_components":choice_components,
"only_choices":self.check_if_only_choice_dish()
"only_choices":only_choices
},
"model":"pos_server.dish",
"id":self.id,
Expand Down
91 changes: 70 additions & 21 deletions pos_server/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
from gift_cards.models import GiftCard
from online_store.models import RejectedOrder
from django.utils.translation import gettext_lazy as _
import logging

logger = logging.getLogger(__name__)

# Create a configparser object
file_dir = os.path.dirname(os.path.abspath(__file__))
Expand Down Expand Up @@ -128,7 +131,12 @@ def order_marking(request):
else:
status_conditions |= ~Q(**{f"{station}_status__in": [0, 1, 2, 3, 4]})
conditions &= status_conditions
orders = Order.objects.filter(conditions)
# Prefetch related data to avoid N+1 queries in collect_order
orders = Order.objects.filter(conditions).prefetch_related(
'orderdish_set__dish',
'delivery',
'authorization'
).select_related('authorization')
print(orders)

# Prepare data for each order
Expand Down Expand Up @@ -328,7 +336,11 @@ def pos(request):
menu = Menu.objects.filter(is_active=True).first()

# Sort dishes by ID before grouping to ensure consistent ordering
dishes = Dish.objects.filter(menu=menu).order_by('id')
# Prefetch components and child_dishes to optimize serialize_with_options
dishes = Dish.objects.filter(menu=menu).prefetch_related(
'components__child_dishes',
'menu'
).order_by('id')

# Group dishes by station
grouped_dishes = defaultdict(list)
Expand Down Expand Up @@ -357,8 +369,20 @@ def pos(request):
new_order = Order(special_instructions=instructions, to_go_order=is_to_go, channel="store")
new_order.name = body["name"] if body["name"].strip() != '' else None
new_order.save()

# Fetch all dishes at once with prefetched data to avoid N+1 queries
dish_ids = list(dish_counts.keys())
dishes_queryset = Dish.objects.filter(id__in=dish_ids).prefetch_related(
'components__child_dishes',
'dishcomponent_set__component'
)
dishes_map = {dish.id: dish for dish in dishes_queryset}

for dish_id, quantity in dish_counts.items():
dish = Dish.objects.get(id=dish_id)
dish = dishes_map.get(dish_id)
if not dish:
logger.warning(f"Dish with id {dish_id} not found while processing POS order")
continue
if check_if_only_choice_dish(dish):
continue
if dish.station == "bar":
Expand Down Expand Up @@ -466,9 +490,11 @@ def check_if_only_choice_dish(dish:Dish):
Returns:
bool: True if all components of the dish point to other dishes, False otherwise.
"""
if not dish.components.all():
# Use prefetch_related to avoid N+1 queries if not already prefetched
components = dish.components.prefetch_related('child_dishes').all()
if not components:
return False
for component in dish.components.all():
for component in components:
if not component.child_dishes.all():
return False
return True
Expand All @@ -493,18 +519,22 @@ def component_choice(request):
"""
dish_id = request.GET.get('dish_id')
if dish_id:
dish = Dish.objects.filter(pk=dish_id).first()
# Prefetch components and child_dishes to avoid N+1 queries
dish = Dish.objects.filter(pk=dish_id).prefetch_related(
'components__child_dishes'
).first()
choice_components = []
for component in dish.components.all():
if component.child_dishes.all():
child_dishes = list(component.child_dishes.all())
if child_dishes:
choices = {
"parent":{
"title":component.title,
"id":component.id
},
"children":[]
}
for child in component.child_dishes.all():
for child in child_dishes:
choices["children"].append({
"title":child.title,
"id":child.id,
Expand Down Expand Up @@ -594,7 +624,11 @@ def day_stats(request):
menu = Dish.objects.all()

# Fetch all orders for the specific date, ordered by timestamp
orders = Order.objects.filter(timestamp__date=day).order_by('timestamp')
# Prefetch related data to avoid N+1 queries
orders = Order.objects.filter(timestamp__date=day).prefetch_related(
'orderdish_set__dish__dishcomponent_set__component__componentingredient_set__ingredient',
'dishes'
).order_by('timestamp')

# Initialize stats dictionary to store various metrics
stats = {
Expand Down Expand Up @@ -626,7 +660,7 @@ def get_15_min_window(dt):
# Group order occasions into 15-minute time windows
time_window = get_15_min_window(order.timestamp)

# Calculate total price of the order
# Calculate total price of the order (uses prefetched data)
order_price = sum(od.quantity * od.dish.price for od in order.orderdish_set.all())

# Update the count and total earnings for the time window
Expand All @@ -644,7 +678,7 @@ def get_15_min_window(dt):
average_prep = data['total_prep_time'] / count
stats['prep_times'][window] = average_prep

# Process each dish in the order
# Process each dish in the order (uses prefetched data)
for item in order.dishes.all():
# Count the quantity of each dish sold
if item.title not in stats["item_stats"]:
Expand All @@ -658,7 +692,7 @@ def get_15_min_window(dt):
else:
stats["stations"][item.station] += 1

# Track the quantity of each component used in the dish
# Track the quantity of each component used in the dish (uses prefetched data)
for dc in item.dishcomponent_set.all():
if dc.component.title not in stats["components"]:
stats["components"][dc.component.title] = [None] * 2
Expand All @@ -667,7 +701,7 @@ def get_15_min_window(dt):
else:
stats["components"][dc.component.title][0] += dc.quantity

# Track the quantity of each ingredient used in the components
# Track the quantity of each ingredient used in the components (uses prefetched data)
for ci in dc.component.componentingredient_set.all():
if ci.ingredient.title not in stats["ingredients"]:
stats['ingredients'][ci.ingredient.title] = [None] * 2
Expand Down Expand Up @@ -737,7 +771,11 @@ def compile_menu(menu):
"bar":[],
"gng":[],
}
for dish in menu.dishes.all().order_by("id"):
# Prefetch related data to avoid N+1 queries
dishes = menu.dishes.prefetch_related(
'dishcomponent_set__component__child_dishes'
).all().order_by("id")
for dish in dishes:
categories[dish.station].append(prettify_dish(dish))
return categories, components_out

Expand All @@ -761,7 +799,8 @@ def prettify_dish(dish):
"price":format_float(dish.price),
"available":(dish.in_stock or dish.force_in_stock) and dish.visible_in_menu,
}
dcs = dish.dishcomponent_set.all()
# Use list to leverage prefetched data if available
dcs = list(dish.dishcomponent_set.all())
for index, dc in enumerate(dcs):
if dc.component.type == "food":
if dc.component.unit_of_measurement == "l" or dc.component.unit_of_measurement == "ml":
Expand All @@ -784,9 +823,11 @@ def prettify_dish(dish):
final_dish["components"] += f"{dc.component.title.lower()}"
if dc.quantity > 1 and not dc.component.type == 'beverage' and not (dc.component.unit_of_measurement == "g" or dc.component.unit_of_measurement == "kg"):
final_dish["components"] += "s"
if dc.component.child_dishes.all():
# Use prefetched child_dishes
child_dishes = list(dc.component.child_dishes.all())
if child_dishes:
final_dish["components"] += _(" (choice of: ")
for choice in dc.component.child_dishes.all():
for choice in child_dishes:
final_dish["components"] += f"{choice.title}/"
final_dish["components"] = final_dish["components"][:-1]
final_dish["components"] += ")"
Expand Down Expand Up @@ -1084,7 +1125,12 @@ def active_orders(request):
active_order_ids = cache.get('active_orders')

# Fetch active orders from database using IDs from the cache
active_orders = Order.objects.filter(id__in=active_order_ids)
# Prefetch related data to avoid N+1 queries in collect_order
active_orders = Order.objects.filter(id__in=active_order_ids).prefetch_related(
'orderdish_set__dish',
'delivery',
'authorization'
).select_related('authorization')
serialized_orders = [collect_order(order) for order in active_orders] # Adjust serialization as needed
return JsonResponse(serialized_orders, safe=False)

Expand Down Expand Up @@ -1134,8 +1180,8 @@ def collect_order(order, done=False):
"""
if not order:
return None
# Fetch related OrderDish instances for each order
order_dishes = OrderDish.objects.filter(order=order)
# Use prefetched OrderDish instances from order.orderdish_set if available
order_dishes = order.orderdish_set.all()

# Prepare dish details for this order
dishes_data = []
Expand All @@ -1147,6 +1193,9 @@ def collect_order(order, done=False):
'station': od.dish.station
})

# Use prefetched delivery data if available
delivery = order.delivery.first() if hasattr(order, 'delivery') else None

# Add the order and its dishes to the orders_data list
return({
'order_id': order.id,
Expand All @@ -1155,7 +1204,7 @@ def collect_order(order, done=False):
'to_go_order':order.to_go_order,
'channel':order.channel,
'phone':order.phone,
'address':order.delivery.first().destination if order.delivery.first() else None,
'address':delivery.destination if delivery else None,
"special_instructions": order.special_instructions,
"timestamp":order.timestamp.isoformat(),
"timestamp_pretty":order.timestamp,
Expand Down
Loading