Menu Item Photos in HotelFinder

HotelFinder can hold its own photo for a menu item and show it by default, even for items whose photos are synced from Aahar POS. The HotelFinder photo takes display priority and is preserved across every Aahar sync. One backend endpoint handles the full lifecycle (upload / replace / delete); no existing API signatures changed.

The problem it solves

Menu items sync from Aahar into the single MenuItem.image field. The sync helper (restaurant/views.pysync_menu_from_aahar) overwrites that field whenever the Aahar filename differs, so any photo uploaded through HotelFinder used to be clobbered on the next sync.

How it works — two image slots

FieldWritten byPurpose
imageAahar sync (unchanged)The POS-provided photo. Still stored, used as fallback.
custom_image (new)The upload endpoint belowHotelFinder-owned photo. Shown in preference to image.

Display rule (in both MenuItemSerializer and GlobalMenuItemSerializer via the effective_image_url() helper):

image_shown = custom_image  if custom_image else  image

The API response key stays image — only its value now prefers the HotelFinder upload. Deleting the custom photo makes display fall back to the Aahar photo automatically. Because the sync only ever writes image, the HotelFinder photo survives every sync with no special handling.

Pillow processing

Every upload passes through _process_menu_image() (restaurant/views.py), which uses Pillow (already a dependency, pillow==11.3.0) to:

The one endpoint

/api/restaurants/<restaurant_id>/menu/<item_id>/image/

(also reachable via the /api/restaurant/ and /restaurant/api/ aliases)

MethodActionBodyReturns
POST / PUTUpload or replace the HotelFinder photomultipart/form-data, field imageThe updated menu item (serialized)
DELETERemove it (falls back to the Aahar photo)The updated menu item (serialized)

Permissions: authenticated Restaurant Owner (of that restaurant) or Admin.

Example

# Upload / replace
curl -X POST -H "Authorization: Bearer <token>" \
     -F image=@dish.jpg \
     http://localhost:8000/api/restaurants/1/menu/42/image/

# Remove (revert to Aahar photo)
curl -X DELETE -H "Authorization: Bearer <token>" \
     http://localhost:8000/api/restaurants/1/menu/42/image/
No changes to existing APIs. The menu list/detail/global endpoints keep their signatures. The serialized response gains an additive custom_image key (harmless to existing clients), and the image key now returns the HotelFinder photo when one exists.

Files changed

FileChange
restaurant/models.pyAdded MenuItem.custom_image field
restaurant/migrations/0024_menuitem_custom_image.pyMigration for the new field
restaurant/serializers.pyeffective_image_url() helper; both get_image() prefer custom_image
restaurant/views.py_process_menu_image() (Pillow) + MenuItemImageView
restaurant/urls.pyRoute for the image endpoint
test_menu_image.pySelf-check for the Pillow processing

Storage: media/menu_items/custom/ (HotelFinder) · media/menu_items/ (Aahar).