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.
Menu items sync from Aahar into the single MenuItem.image field. The sync helper
(restaurant/views.py → sync_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.
| Field | Written by | Purpose |
|---|---|---|
image | Aahar sync (unchanged) | The POS-provided photo. Still stored, used as fallback. |
custom_image (new) | The upload endpoint below | HotelFinder-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.
Every upload passes through _process_menu_image() (restaurant/views.py),
which uses Pillow (already a dependency, pillow==11.3.0) to:
Image.verify() rejects corrupt or non-image files (HTTP 400).thumbnail((1024, 1024)) caps the longest side at 1024px./api/restaurants/<restaurant_id>/menu/<item_id>/image/
(also reachable via the /api/restaurant/ and /restaurant/api/ aliases)
| Method | Action | Body | Returns |
|---|---|---|---|
| POST / PUT | Upload or replace the HotelFinder photo | multipart/form-data, field image | The updated menu item (serialized) |
| DELETE | Remove it (falls back to the Aahar photo) | — | The updated menu item (serialized) |
Permissions: authenticated Restaurant Owner (of that restaurant) or Admin.
# 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/
custom_image key (harmless to
existing clients), and the image key now returns the HotelFinder photo when one exists.
| File | Change |
|---|---|
restaurant/models.py | Added MenuItem.custom_image field |
restaurant/migrations/0024_menuitem_custom_image.py | Migration for the new field |
restaurant/serializers.py | effective_image_url() helper; both get_image() prefer custom_image |
restaurant/views.py | _process_menu_image() (Pillow) + MenuItemImageView |
restaurant/urls.py | Route for the image endpoint |
test_menu_image.py | Self-check for the Pillow processing |
Storage: media/menu_items/custom/ (HotelFinder) · media/menu_items/ (Aahar).