- Python 59.9%
- HTML 39.4%
- CSS 0.5%
- Makefile 0.1%
- Shell 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .claude | ||
| conductor | ||
| conductor-pi | ||
| core | ||
| deploy | ||
| docs | ||
| mealplanner | ||
| scripts | ||
| templates | ||
| .gitignore | ||
| .python-version | ||
| AGENTS.md | ||
| Makefile | ||
| manage.py | ||
| mealplanner-spec.md | ||
| plan.md | ||
| pyproject.toml | ||
| README.md | ||
| TODO.md | ||
| uv.lock | ||
Meal Planner
A family meal planning application built with Django, HTMX, and Tailwind CSS.
Local Development
Prerequisites
- uv (Python package manager)
- Tailwind CLI binary: Download via:
(Note: Adjust URL for non-Linux platforms)curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 && chmod +x tailwindcss-linux-x64 && mv tailwindcss-linux-x64 tailwindcss
Running the Application
-
Install dependencies:
uv sync -
Database setup:
uv run manage.py migrate uv run manage.py seed_data -
Start the development server:
uv run manage.py runserver -
Watch for CSS changes (in a separate terminal):
make css-watch
CSS Build Commands
make css: Performs a one-time minified build ofstyles.css.make css-watch: Watchesinput.cssand all templates for changes and rebuilds CSS automatically.
API Access
The Meal Planner includes a RESTful API with comprehensive endpoints for all models.
Authentication
The API supports both session-based and token-based (JWT) authentication:
Session Authentication (Web Browsers)
# Login to establish session
curl -X POST http://localhost:8000/accounts/login/ \
-d "username=youruser&password=yourpass" \
--cookie-jar cookies.txt
# Use session cookie for API requests
curl http://localhost:8000/api/v1/recipes/ \
--cookie cookies.txt
Token Authentication (Mobile Apps, CLI, Third-Party Services)
Important: Always include -H "Accept: application/json" to get JSON responses instead of HTML.
# Get JWT tokens (note: Accept header is required for JSON response)
curl -s -X POST http://localhost:8000/api/v1/token/ \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"username":"youruser","password":"yourpass"}'
# Response includes access and refresh tokens:
# {
# "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
# "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
# }
# Use access token for API requests
curl -s http://localhost:8000/api/v1/recipes/ \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Accept: application/json"
# Refresh access token when expired
curl -s -X POST http://localhost:8000/api/v1/token/refresh/ \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}'
CLI Usage Examples (Bash)
For convenient CLI usage, save the token in a variable and reuse it:
# Login and save token to variable
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/token/ \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"username":"matt","password":"euShuuZog1ahfaiVa2"}' | jq -r '.access')
# Now use $TOKEN for subsequent requests
curl -s http://localhost:8000/api/v1/recipes/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
# List all recipes with pretty printing
curl -s http://localhost:8000/api/v1/recipes/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
# Create a new recipe
curl -s -X POST http://localhost:8000/api/v1/recipes/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "My New Recipe",
"meal_type": 1,
"difficulty": 2,
"instructions": "Step 1: Do this. Step 2: Do that."
}'
# Get a specific recipe
curl -s http://localhost:8000/api/v1/recipes/1/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
# Update a recipe
curl -s -X PUT http://localhost:8000/api/v1/recipes/1/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"name": "Updated Recipe Name"}'
# Delete a recipe
curl -s -X DELETE http://localhost:8000/api/v1/recipes/1/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
Week Plan Examples
# Assuming $TOKEN is set from login above
# List all week plans
curl -s http://localhost:8000/api/v1/week-plans/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
# Create a new week plan
curl -s -X POST http://localhost:8000/api/v1/week-plans/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"start_date": "2026-06-01"}'
# Shuffle meals for a week plan (custom action)
curl -s -X POST http://localhost:8000/api/v1/week-plans/1/shuffle/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
# Get planned meals for a week plan
curl -s http://localhost:8000/api/v1/week-plans/1/planned_meals/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
Shopping List Examples
# Assuming $TOKEN is set from login above
# List all shopping lists
curl -s http://localhost:8000/api/v1/shopping-lists/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
# Generate shopping list from a week plan
curl -s -X POST http://localhost:8000/api/v1/shopping-lists/1/generate/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"week_plan": 1, "store": 1}'
# Get items for a shopping list
curl -s http://localhost:8000/api/v1/shopping-lists/1/items/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
# Toggle check status on an item
curl -s -X POST http://localhost:8000/api/v1/shopping-list-items/1/toggle_check/ \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
Filtering Examples
# Filter recipes by meal type
curl -s "http://localhost:8000/api/v1/recipes/?meal_type=1" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
# Filter recipes by difficulty
curl -s "http://localhost:8000/api/v1/recipes/?difficulty=2" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
# Search recipes by name
curl -s "http://localhost:8000/api/v1/recipes/?search=chicken" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
# Combine filters
curl -s "http://localhost:8000/api/v1/recipes/?meal_type=1&difficulty=2&search=pasta" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" | jq '.'
Token Management
The Meal Planner API uses JWT tokens with refresh tokens for secure authentication. This means:
- Access Token: Valid for 5 minutes, used for API requests
- Refresh Token: Valid for 1 day, used to get new access tokens without re-entering credentials
- Token Rotation: Refresh tokens rotate on use for security (
ROTATE_REFRESH_TOKENS=True)
Why use refresh tokens? Instead of re-entering your username/password every 5 minutes, you use the refresh token to silently obtain new access tokens.
Token Refresh Workflow
# Step 1: Login to get both tokens
TOKEN_RESPONSE=$(curl -s -X POST http://localhost:8000/api/v1/token/ \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"username":"matt","password":"euShuuZog1ahfaiVa2"}')
# Extract tokens
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access')
REFRESH_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.refresh')
# Step 2: Use access token for API calls (expires in 5 minutes)
curl -s http://localhost:8000/api/v1/recipes/ \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json"
# Step 3: When access token expires, refresh it WITHOUT credentials
# This returns a NEW access token and a ROTATED refresh token
REFRESH_RESPONSE=$(curl -s -X POST http://localhost:8000/api/v1/token/refresh/ \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "{\"refresh\":\"$REFRESH_TOKEN\"}")
# Extract new tokens
ACCESS_TOKEN=$(echo $REFRESH_RESPONSE | jq -r '.access')
REFRESH_TOKEN=$(echo $REFRESH_RESPONSE | jq -r '.refresh') # This is the NEW refresh token
# Step 4: Continue using the new access token
curl -s http://localhost:8000/api/v1/recipes/ \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json"
# Step 5: Verify token validity (optional)
curl -s -X POST http://localhost:8000/api/v1/token/verify/ \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "{\"token\":\"$ACCESS_TOKEN\"}"
Important Notes:
- Always use the latest refresh token - After refreshing, the old refresh token is invalidated
- Store refresh tokens securely - They can be used to get new access tokens
- Access tokens expire quickly (5 min) but refresh tokens last longer (1 day)
- No credentials needed for refresh - Just the refresh token itself
- Rate limited - Token endpoints have strict throttling (10/min for
/token/, 30/min for/token/refresh/)
Token Management Examples
# Get both access and refresh tokens
TOKEN_RESPONSE=$(curl -s -X POST http://localhost:8000/api/v1/token/ \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"username":"matt","password":"euShuuZog1ahfaiVa2"}')
# Extract both tokens
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access')
REFRESH_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.refresh')
# Use access token
curl -s http://localhost:8000/api/v1/recipes/ \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept: application/json"
# When access token expires, refresh it
NEW_TOKENS=$(curl -s -X POST http://localhost:8000/api/v1/token/refresh/ \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "{\"refresh\":\"$REFRESH_TOKEN\"}")
# Update tokens from the response
ACCESS_TOKEN=$(echo $NEW_TOKENS | jq -r '.access')
REFRESH_TOKEN=$(echo $NEW_TOKENS | jq -r '.refresh')
API Documentation
- Swagger UI:
http://localhost:8000/api/v1/schema/swagger-ui/ - OpenAPI Schema:
http://localhost:8000/api/v1/schema/
API Endpoints
All endpoints are under /api/v1/ and require authentication. See API_IMPLEMENTATION_STATUS.md for complete endpoint documentation.
Quick Reference
| Resource | Endpoint | Methods |
|---|---|---|
| Meal Types | /api/v1/meal-types/ |
GET, POST, PUT, PATCH, DELETE |
| Shopping Categories | /api/v1/shopping-categories/ |
GET, POST, PUT, PATCH, DELETE |
| Stores | /api/v1/stores/ |
GET, POST, PUT, PATCH, DELETE |
| Ingredients | /api/v1/ingredients/ |
GET, POST, PUT, PATCH, DELETE |
| Recipes | /api/v1/recipes/ |
GET, POST, PUT, PATCH, DELETE |
| Week Plans | /api/v1/week-plans/ |
GET, POST, PUT, PATCH, DELETE |
| Planned Meals | /api/v1/planned-meals/ |
GET, POST, PUT, PATCH, DELETE |
| Shopping Lists | /api/v1/shopping-lists/ |
GET, POST, PUT, PATCH, DELETE |
| Shopping List Items | /api/v1/shopping-list-items/ |
GET, POST, PUT, PATCH, DELETE |
Custom Actions
| Action | Endpoint | Method |
|---|---|---|
| Shuffle week plan | /api/v1/week-plans/{id}/shuffle/ |
POST |
| Generate shopping list | /api/v1/shopping-lists/{id}/generate/ |
POST |
| Toggle pin on meal | /api/v1/planned-meals/{id}/toggle_pin/ |
POST |
| Toggle check on item | /api/v1/shopping-list-items/{id}/toggle_check/ |
POST |
| List planned meals | /api/v1/week-plans/{id}/planned_meals/ |
GET |
| List shopping items | /api/v1/shopping-lists/{id}/items/ |
GET |
Token Endpoints
| Endpoint | Method | Description |
|---|---|---|
/api/v1/token/ |
POST | Obtain access and refresh tokens |
/api/v1/token/refresh/ |
POST | Refresh access token |
/api/v1/token/verify/ |
POST | Verify token validity |