Hello! In our last lesson, we successfully secured our application's views, ensuring that only logged-in users can access pages like the task list. This was a crucial step in building a secure multi-user application.
However, a significant issue remains: while we're checking if a user is logged in, we're not yet separating their data. Any authenticated user would currently see the exact same list of tasks. Our goal today is to fix that.
This lesson will teach you how to associate application data with the currently logged-in user. By the end, you'll be able to:
- Link your
Taskmodel to Django's built-inUsermodel. - Automatically assign the logged-in user as the owner of any new task they create.
- Filter the task list to show only the tasks belonging to the currently logged-in user.
This will transform your application from a single-user prototype into a true multi-user platform.
1. The Foundation: Linking Models with a ForeignKey
The first step is to create a relationship between your Task data and a User. In database terms, this is a one-to-many relationship: one user can have many tasks. Django represents this relationship using a ForeignKey.
You'll need to modify your Task model to include a field that points to the User model.
Get Done ✅ : A step-by-step guide in building a Django To-Do List
The article 'Get Done ✅' provides a perfect example of a Task model for a to-do list application, which is very similar to your own project goal. This section shows exactly how to add the user relationship.
Read 'Step 2: Define Models'. Pay close attention to the Task model definition. Specifically, note the user field: user = models.ForeignKey(User, on_delete=models.CASCADE).
As you saw in the article, the key is adding this line to your models.py:
# your_app/models.py
from django.db import models
from django.contrib.auth.models import User # Import the User model
class Task(models.Model):
# ... your other fields like title, description, etc.
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
A quick breakdown:
from django.contrib.auth.models import User: We must first import the built-inUsermodel that Django's authentication system provides.models.ForeignKey(User, ...): This creates the link. EachTaskobject will now be associated with aUserobject.on_delete=models.CASCADE: This tells the database what to do if a user is deleted.CASCADEmeans "if the user is deleted, delete all of their tasks too." This is a common and logical choice for this kind of relationship.
Applying the Change
Whenever you change a model, you must update the database schema. This is a two-step process:
python manage.py makemigrations: Django inspects your models, detects the change, and creates a "migration" file—a Python script that describes the necessary database changes.python manage.py migrate: Django runs the migration file, applying the changes to your actual database (in this case, adding auser_idcolumn to your task table).

2. Automatically Assigning the User on Task Creation
Now that our Task model can store a user, we need to populate that field when a new task is created. It would be bad practice (and insecure) to show a dropdown list of all users and ask the creator to select themselves. Instead, we should do it automatically in the background.
The request object that is available in every view contains the currently logged-in user as request.user. We can use this to our advantage.
In a Class-Based View like CreateView, the best place to inject this logic is by overriding the form_valid method. This method is called after the form has been successfully validated but just before the object is saved to the database.
Get Done ✅ : A step-by-step guide in building a Django To-Do List
Let's return to the 'Get Done ✅' article. It provides a crystal-clear implementation for this exact problem within a TaskCreate view.
Find the TaskCreate view in the code block under 'Step 4: Define Views'. Focus on the form_valid method. This short method is the key to associating the new task with the logged-in user.
The pattern is elegantly simple:
# your_app/views.py
from django.views.generic import CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
class TaskCreateView(LoginRequiredMixin, CreateView):
# ... your other attributes like model, form_class, success_url
def form_valid(self, form):
# Before saving the form to the database,
# set the 'user' field of the model instance to the current user.
form.instance.user = self.request.user
# Now, proceed with the default behavior (saving the object).
return super().form_valid(form)
By adding this method, you are telling Django: "Before you finish creating this task, assign the user from the current request to its user field." It's that straightforward.
For a broader understanding, the Net Ninja video below explains the same concept but for a function-based view using form.save(commit=False). The principle is identical: get an instance of the object before it's saved, modify it, and then save it.
Django Tutorial #27 - Foreign Keys
This video demonstrates how to associate a created 'article' with the logged-in user. While it uses a function-based view, the core logic of intercepting the save process is universal and reinforces what you've just learned.
Watch from 04:29 to 06:50. Notice the pattern: form.save(commit=False) to get the object instance without saving it to the database yet. instance.author = request.user to assign the user. instance.save() to finally commit the object to the database.
3. Showing Users Only Their Own Data
We're halfway there. Users can now create tasks that are correctly assigned to them. But the TaskListView will still show all tasks from all users. We need to filter the list.
To do this, we need to modify the set of objects—the QuerySet—that the ListView works with. The best way to do this in a CBV is by overriding the get_queryset method.
# your_app/views.py
from django.views.generic import ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Task
class TaskListView(LoginRequiredMixin, ListView):
model = Task
# ... other attributes
def get_queryset(self):
# The base queryset is all Task objects.
# We filter it to get only the tasks where the 'user' field
# matches the user from the current request.
return Task.objects.filter(user=self.request.user)
The filter(user=self.request.user) call is the heart of this operation. It tells the Django ORM (Object-Relational Mapper) to generate a database query that essentially says, "SELECT all tasks WHERE the user_id column matches the ID of the currently logged-in user."
The following video explains how filtering works on foreign key relationships, giving you a deeper look into the ORM's power.
Django ORM - QuerySet Filtering and Lookups / Ordering and Slicing QuerySets
This clip from BugBytes explains how the ORM handles filtering on related models. It uses a restaurant/rating example, but the principle Model.objects.filter(related_model=some_value) is exactly what we are using.
Watch from 35:48 to 37:40. The key takeaway is how Django translates a filter on a foreign key field (restaurant) into an SQL JOIN statement to fetch the correct data. This gives you a glimpse of the powerful work the ORM does for you.
Test your understanding!
Imagine your application also has a Note model for users to write private notes. The Note model has a ForeignKey to User called author.
- In your
NoteCreateView, how would you modify it to automatically assign the current user as theauthorof a new note? - In your
NoteListView, which method would you override to ensure users only see their own notes, and what would the code inside it be?
Show answer
-
You would override the
form_validmethod inNoteCreateView:def form_valid(self, form): form.instance.author = self.request.user return super().form_valid(form) -
You would override the
get_querysetmethod inNoteListView:def get_queryset(self): return Note.objects.filter(author=self.request.user)
Conclusion
Fantastic! You have now implemented one of the most fundamental features of any multi-user web application. By linking data to users and filtering your views, you've ensured data privacy and created a personalized experience for each user.
Key Takeaways:
- Link Models: Use
models.ForeignKey(User, on_delete=models.CASCADE)to create a relationship between your application's data and a user. - Update the Database: Always run
makemigrationsandmigrateafter modifying your models. - Assign on Create: In a
CreateView, overrideform_validand setform.instance.user = self.request.userto automatically assign ownership. - Filter on Read: In a
ListView, overrideget_querysetand return a filtered set of objects, e.g.,MyModel.objects.filter(user=self.request.user).
Preview of the Next Lesson:
We've made huge progress. Users can create tasks and see only their own list. But there is still one more security hole. What if a malicious user guesses the URL for another user's task? For example, if User A tries to access /task/5/update/, and task #5 belongs to User B, our current setup won't stop them.
In the next lesson, we will plug this final hole by implementing authorization rules to ensure users can only view and modify their own data, preventing them from accessing each other's items via direct URL manipulation.