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

Project Goals

FeatureURL
Signup / login / logout/accounts/
Todo list (own items only)/todos/
Add todoPOST on list or /todos/add/
Toggle done / deletePOST actions
Testspython manage.py test todos

Step 1: Todo App and Model

bash
python manage.py startapp todos

config/settings/base.py:

python
INSTALLED_APPS = [
    ...
    "todos.apps.TodosConfig",
]

todos/models.py:

bash
python manage.py makemigrations todos
python manage.py migrate

Code explanation:

  • owner FK ensures each row belongs to one user
  • CASCADE deletes 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 guessing pk
  • Toggle/delete use POST—avoid destructive GET links

Step 4: Templates

todos/templates/todos/list.html:

Step 5: URLs

todos/urls.py:

python
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:

python
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:

python
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:

bash
python manage.py test todos

Verification 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.