Project: Todo App With Authentication
Introduction
This project combines user accounts and personal todo lists in one Django app—signup, login, CRUD on todos scoped per user, forms with CSRF, and tests. It reinforces patterns from Authentication, Forms, and Testing without repeating the blog domain from Project: Blog MTV.
Prerequisites
- User Authentication and Permissions
- Forms and CSRF
- Testing Django Applications
- Compare with Flask Todo project for microframework style
Project Goals
| Feature | URL |
|---|---|
| Signup / login / logout | /accounts/ |
| Todo list (own items only) | /todos/ |
| Add todo | POST on list or /todos/add/ |
| Toggle done / delete | POST actions |
| Tests | python manage.py test todos |
Step 1: Todo App and Model
python manage.py startapp todosconfig/settings/base.py:
INSTALLED_APPS = [
...
"todos.apps.TodosConfig",
]todos/models.py:
python manage.py makemigrations todos
python manage.py migrateCode explanation:
ownerFK ensures each row belongs to one userCASCADEdeletes todos when user is deleted
Step 2: Forms
todos/forms.py:
Step 3: Views (FBV)
todos/views.py:
Code explanation:
get_object_or_404(..., owner=request.user)prevents accessing another user's todo by guessingpk- Toggle/delete use POST—avoid destructive GET links
Step 4: Templates
todos/templates/todos/list.html:
Step 5: URLs
todos/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path("", views.todo_list, name="todo-list"),
path("<int:pk>/toggle/", views.todo_toggle, name="todo-toggle"),
path("<int:pk>/delete/", views.todo_delete, name="todo-delete"),
]config/urls.py:
urlpatterns = [
path("admin/", admin.site.urls),
path("accounts/", include("django.contrib.auth.urls")),
path("accounts/signup/", views.signup, name="signup"), # from auth chapter
path("todos/", include("todos.urls")),
path("", RedirectView.as_view(pattern_name="todo-list")),
]Set LOGIN_URL = "login" and LOGIN_REDIRECT_URL = "todo-list".
Step 6: Admin (Optional)
todos/admin.py:
from django.contrib import admin
from .models import Todo
@admin.register(Todo)
class TodoAdmin(admin.ModelAdmin):
list_display = ["title", "owner", "done", "created_at"]
list_filter = ["done"]
search_fields = ["title", "owner__username"]Staff can inspect data; normal users use /todos/.
Step 7: Tests
todos/tests/test_views.py:
Run:
python manage.py test todosVerification Checklist
- Signup creates user; login reaches
/todos/ - Adding a todo persists for current user only
- Toggle and delete require POST and ownership
- Tests pass; no CSRF errors on forms
FAQ
CBV instead of FBV?
LoginRequiredMixin + ListView / CreateView—same scoping in get_queryset().
Flash messages?
Use django.contrib.messages after add/delete—messages framework.
HTMX partial updates?
Return fragment templates on POST—optional UX upgrade.
API todos?
Add DRF ModelViewSet with get_queryset() filtered by user—REST API blog.
Compare Flask todo?
Flask uses current_user and @login_required similarly—Flask todo.