A hands-on workshop project for Alamo Tech Collective (in collaboration with Alamo Python) that demonstrates progressive enhancement by building the same to-do app twice: once with traditional Django, and once with HTMX.
This project showcases the difference between:
- Django-only version (main branch): Traditional full-page reloads
- HTMX-enhanced version (htmx-enhanced branch): Instant partial-page updates
Same backend code. Dramatically different user experience.
- Docker Desktop installed and running
- Git (for cloning the repository)
- That's it! No Python, Django, or other dependencies needed locally.
git clone https://github.com/Alamo-Tech-Collective/django-htmx-todo.git
cd django-htmx-tododocker compose upWait for the startup messages. You should see:
Starting development server at http://0.0.0.0:8000/
Visit: http://localhost:8000
You should see the to-do app running!
- Add some tasks
- Check/uncheck tasks (notice the full-page reload)
- Delete tasks (notice the full-page reload)
- Add 6+ tasks to see pagination in action
Key observation: Every action causes a full-page refresh. This is the traditional Django experience.
Press Ctrl+C in the terminal, or run:
docker compose downThe HTMX-enhanced version is on a separate branch:
# Stop the current app
docker compose down
# Switch to the HTMX branch
git checkout htmx-enhanced
# Start the HTMX version
docker compose upNow try the same actions - notice how there are no full-page reloads!
Want to see exactly what it took to add HTMX? It's a small diff — the backend barely changes:
# Same backend, minimal template tweaks - no full rewrite
git diff main htmx-enhanceddjango-htmx-todo/
├── README.md # This file
├── CLAUDE.md # Project context and guidelines
├── docker-compose.yml # Docker orchestration
├── Dockerfile # Container definition
├── requirements.txt # Python dependencies
├── manage.py # Django management script
├── todo_project/ # Django project settings
│ ├── settings.py
│ └── urls.py
└── tasks/ # To-do app
├── models.py # Task model
├── views.py # View logic
├── forms.py # Django forms
├── urls.py # URL routing
├── admin.py # Django admin
├── templates/ # HTML templates
│ └── tasks/
│ ├── base.html
│ ├── task_list.html
│ └── partials/ # HTMX partials (htmx-enhanced branch only)
└── static/ # CSS and JS
└── css/
└── style.css
- ✅ Create tasks
- ✅ View tasks (with pagination - 5 per page)
- ✅ Toggle task completion
- ✅ Delete tasks
- ✅ Django admin interface
- Traditional POST/redirect/GET pattern
- Full-page reloads on every action
- Standard Django forms and views
- Partial page updates
- No full-page reloads
- Same backend views (minimal changes)
- Instant, smooth interactions
This project is designed to teach:
- Progressive enhancement - Same templates, minimal changes
- When to use HTMX vs full JavaScript frameworks
- Real-world patterns you can use in your projects
- The UX difference between traditional and HTMX-enhanced Django
Plan for about 90–120 minutes, hands-on the whole way. Here's the shape of it:
Phase 1 — Django-only (~45–60 min). Everyone gets the app running with
docker compose up, then we walk the code (models, views, templates, forms). You'll
use the app and feel the clunkiness — every add, toggle, and delete triggers a
full-page reload. We'll pair up and call out what feels slow.
Phase 2 — HTMX enhancement (~30–45 min). We add the HTMX CDN (one script tag), convert task completion to a partial update together, then you take a crack at task deletion yourself. Same backend, instant updates. We'll diff the two versions side by side.
Phase 3 — Q&A and wrap-up (~15 min). When to reach for HTMX vs. React/Vue, scaling considerations, and where to go next.
Come with Docker Desktop installed and the repo cloned (see Setup above). The less time we spend on environment issues, the more we spend building.
Problem: Cannot connect to the Docker daemon
# Solution: Make sure Docker Desktop is running
# Then try again: docker compose upProblem: Port 8000 already in use
# Solution: Stop the other process using port 8000, or change the port in docker-compose.yml
# Find what's using port 8000:
lsof -i :8000
# Kill it or change docker-compose.yml ports to "8001:8000"Problem: Changes not appearing
# Solution: Rebuild the container
docker compose down
docker compose up --buildProblem: No tasks showing up
# Solution: Check if migrations ran
docker compose exec web python manage.py migrateProblem: CSS not loading
# Solution: Collect static files
docker compose exec web python manage.py collectstatic --noinput
# Or just refresh the page (volume mounting should auto-update)# Create a superuser for Django admin
docker compose exec web python manage.py createsuperuser
# Access Django admin at: http://localhost:8000/admin
# Create migrations (if you modify models)
docker compose exec web python manage.py makemigrations
# Apply migrations
docker compose exec web python manage.py migrate
# Django shell
docker compose exec web python manage.py shell# Follow logs in real-time
docker compose logs -f web
# View last 50 lines
docker compose logs --tail=50 web- Backend: Django 5.2 (LTS)
- Database: SQLite (simple, no external dependencies)
- Frontend: Django templates + HTMX 2.x (htmx-enhanced branch)
- Styling: Minimal custom CSS
- Container: Docker + Docker Compose
This is an educational project for Alamo Tech Collective workshops. Contributions are welcome!
MIT License - feel free to use this for your own learning and projects!
- Workshop attendees: Ask during the workshop or in the Slack channel
- Everyone else: Open an issue on GitHub
- Visit us: Alamo Tech Collective
Built with ❤️ by Alamo Tech Collective & Alamo Python community