LWAZI // ENGR
← Back to Gallery

Little Lemon

Live
HTML Javascript CSS Python Django
View GitHub Repository →

// System Specification & Design

Little Lemon — Web Application Specification Type: Full-stack restaurant management web application Purpose: Portfolio deployment (Meta Backend Developer Capstone, cleaned up and hardened for public deployment) Status: Deployed to Render (free tier) 1. Overview Little Lemon is a Django-based restaurant web application combining server-rendered public pages with a REST API backend for menu management and table reservations. It's a hybrid monolith: Django serves both HTML templates (for browsing/booking) and JSON API endpoints (for programmatic access and the booking page's dynamic JS). 2. Tech Stack | Layer | Technology | |---|---| | Backend framework | Django 6.0.x | | API framework | Django REST Framework 3.17.x | | Authentication | Djoser + DRF Token Authentication | | Database | SQLite (file-based, ephemeral on host) | | Media storage | Cloudinary (persistent image hosting) | | Static file serving | WhiteNoise | | WSGI server (production) | Gunicorn | | Dependency management | Pipenv | | Frontend | Django Templates + vanilla JavaScript (no frontend framework) | | Hosting | Render (free tier, Python native runtime) | | Python version | 3.13 | 3. Data Model Booking | Field | Type | Notes | |---|---|---| | id | AutoField | Primary key | | first_name | CharField(255) | | | reservation_date | DateField | | | reservation_slot | IntegerField | Hour of day, default 10 (10:00–20:00 range used by frontend) | Constraint: unique_together = ('reservation_slot', 'reservation_date') — prevents double-booking the same time slot on the same day. Menu | Field | Type | Notes | |---|---|---| | id | AutoField | Primary key | | title | CharField(255) | | | price | DecimalField(10,2) | | | inventory | IntegerField | | | image | ImageField | Optional; stored via Cloudinary in production | 4. API Endpoints Base path: /restaurant/ | Endpoint | Methods | Auth Required | Description | |---|---|---|---| | /restaurant/api/menu/ | GET, POST | GET: none · POST: token | List menu items / create new item | | /restaurant/api/menu/<id>/ | GET, PUT, PATCH, DELETE | GET: none · writes: token | Retrieve/update/delete a single menu item | | /restaurant/api/bookings/ | GET, POST | Token (all methods) | List bookings (optionally filtered by ?date=YYYY-MM-DD) / create booking | | /restaurant/api/bookings/<id>/ | GET, PUT, PATCH, DELETE | Token (all methods) | Retrieve/update/delete a single booking | | /restaurant/api-token-auth/ | POST | — | Exchange username/password for an auth token | | /restaurant/health/ | GET | None | Health check — returns {"status": "ok"} | Auth endpoints (via Djoser, mounted at /auth/) Standard Djoser-generated user management endpoints (registration, current-user detail, token login/logout) — not hand-built, provided by the djoser package. Page routes (server-rendered HTML) | Route | View | Template | |---|---|---| | /restaurant/ | home | index.html | | /restaurant/about/ | about | about.html | | /restaurant/menu/ | menu_page | menu.html | | /restaurant/reservations/ | bookings | book.html | Note: the bare root / is not currently routed (404s) — the app is namespaced entirely under /restaurant/. 5. Authorization Model | Resource | Public (anonymous) | Authenticated | |---|---|---| | Menu — read | ✅ Allowed | ✅ Allowed | | Menu — write (create/update/delete) | ❌ Blocked | ✅ Allowed | | Bookings — read | ❌ Blocked | ✅ Allowed | | Bookings — write | ❌ Blocked | ✅ Allowed | Implemented via DRF's IsAuthenticatedOrReadOnly (menu) and IsAuthenticated (bookings) permission classes. Authentication is token-based (rest_framework.authentication.TokenAuthentication), obtained via /restaurant/api-token-auth/ or Djoser's endpoints. 6. Frontend Behavior
  • Booking page (book.html): vanilla JS handles the reservation flow client-side — fetches existing bookings for a selected date via the API, renders available/reserved time slots (10:00–20:00, hourly), and submits new bookings via fetch() with CSRF token handling.
  • Menu page (menu.html): server-rendered list of all menu items with image (or placeholder) and price.
  • Static assets: served via WhiteNoise with compressed manifest storage in production.
  • Media assets: menu item images served from Cloudinary CDN.
7. Testing 23 automated tests, organized as:
  • restaurant/tests/test_models.py — model string representations, unique_together constraint enforcement
  • restaurant/tests/test_api.py — Menu API permission boundaries (anonymous read/blocked write, authenticated write), Booking API auth requirements, duplicate-slot rejection, date-filtered queries, serializer output validation
  • restaurant/tests/test_views.py — page rendering for all four template views, health check endpoint
Run via: python manage.py test restaurant 8. Environment Configuration All sensitive/environment-specific values are externalized via django-environ, loaded from a .env file (local) or host-provided environment variables (production): | Variable | Purpose | |---|---| | SECRET_KEY | Django cryptographic signing key | | DEBUG | Debug mode toggle (False in production) | | ALLOWED_HOSTS | Permitted request Host headers | | CLOUDINARY_URL | Cloudinary account credentials for media storage | | DATABASE_URL | Optional — defaults to local SQLite if unset | 9. Deployment Architecture
  • Host: Render, free-tier web service
  • Build process: custom build.sh — installs dependencies via Pipenv, runs collectstatic, runs migrate
  • Runtime: Gunicorn (gunicorn Littlelemon.wsgi:application --bind 0.0.0.0:$PORT)
  • Python version pinned via .python-version (3.13) to match Pipfile.lock
  • Database persistence: SQLite is file-based and not persistent across redeploys/restarts on Render's free tier (no attached disk). This is an accepted tradeoff — booking data is demo-only and expected to periodically reset; menu data is intended to be seeded via a data migration so the app never appears empty.
  • Media persistence: solved independently via Cloudinary, which is unaffected by host filesystem resets.
  • CORS: not configured — app is same-origin (API and frontend served from the same Django instance). Would need reconsideration if a separate frontend is ever built against this API.
10. Known Limitations (by design, for this deployment context)
  • No persistent relational database — acceptable for a demo/portfolio deployment, not suitable as-is for a production restaurant system handling real bookings.
  • Free-tier hosting spins down after 15 minutes of inactivity; first request after idle incurs a ~30–60s cold start.
  • No CI/CD pipeline beyond Render's git-push auto-deploy.
  • No rate limiting on public-read endpoints.
  • Bare / route unhandled (404) — app is scoped under /restaurant/.
11. Summary Stats
  • Models: 2 (Booking, Menu)
  • Custom REST endpoints: 5 resource endpoints + 1 health check
  • Auth-provided endpoints: Djoser's standard set (registration, token login/logout, user detail)
  • Automated tests: 23, all passing
  • Third-party services integrated: Cloudinary (media storage)

// Deployment System Status