Django overriding get_context_data() not rendering in template

Viewed 145

I'm learning to work with CBV's and I am currently working on a simple todo list, I want to get specific user data for specific users. The way I am doing this is by overriding the get_context_data() method in my ListView class, but the data is not showing in my template. I have created multiple users and created multiple tasks, but still does not work even though I can access them through the admin page.

Here is my code:

views.py:

class TaskList(LoginRequiredMixin, ListView):
    model = Task
    template_name = 'app/home.html'
    context_object_name = 'tasks'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['tasks'] = context['tasks'].filter(user=self.request.user)
        context['count'] = context['tasks'].filter(
            complete=False).count()
        return context

models.py:

from django.db import models
from django.contrib.auth.models import User


class Task(models.Model):
    user = models.ForeignKey(
        User, on_delete=models.CASCADE, blank=True, null=True)
    title = models.CharField(max_length=150)
    description = models.TextField(max_length=500)
    complete = models.BooleanField(default=False)
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.title

    # Order by completion
    class Meta:
        ordering = ['complete']

urls.py:

from django.urls import path

from .views import (
    TaskList,
    TaskDetail,
    TaskCreate,
    TaskUpdate,
    TaskDelete,

    UserLogin,
    UserLogout
)

urlpatterns = [
    path('login/', UserLogin.as_view(), name='login'),
    path('logout/', UserLogout.as_view(), name='logout'),


    path('', TaskList.as_view(), name='home'),
    path('task-detail/<int:pk>/', TaskDetail.as_view(), name='task-detail'),
    path('task-create/', TaskCreate.as_view(), name='task-create'),
    path('task-update/<int:pk>/', TaskUpdate.as_view(), name='task-update'),
    path('task-delete/<int:pk>/', TaskDelete.as_view(), name='task-delete')
]

home.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>To Do List</title>
</head>
<body>
    {% if request.user.is_authenticated %}
        <p>{{ request.user }}</p>
        <a href="{% url 'logout' %}">Logout</a>
    {% else %}
        <a href="{% url 'login' %}">Login</a>
    {% endif %}    
    <hr>
    <a href="{% url 'task-create' %}">Add Task</a>
    <table>
        <tr>
            <th>
                Items
            </th>
        </tr>
        {% for task in tasks %}
        <tr>
            <td>
                {{ task }}
            </td>
            <td>
                <a href="{% url 'task-detail' task.id %}">View</a>
            </td>
            <td>
                <a href="{% url 'task-update' task.id %}">Edit</a>
            </td>
            <td>
                <a href="{% url 'task-delete' task.id %}">Delete</a>
            </td>
        </tr>
        {% endfor %}

    </table>
</body>
</html>

I noticed that when I comment out the line: context['tasks'] = context['tasks'].filter(user=self.request.user) the data does show up in my template but not user specific. What does this mean? Also, adding other context does seem to work, but not when I try getting user specific data. I think I'm missing something small but I can't figure it out.

1 Answers

filter() method or any method of querysets must be applied on models not on empty dictionary keys (eg:tasks).

Try this:

Views.py

class TaskList(LoginRequiredMixin, ListView):
    model = Task
    template_name = 'app/home.html'
    context_object_name = 'all_tasks'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['tasks'] = self.model.objects.filter(user=self.request.user)
        context['count'] = self.model.objects.filter(
            complete=False).count()
        return context

all_tasks will give all objects of Task model as it is ListView which gives all instances, whereas tasks which is in context of get_context_data will give user specific tasks.

Then, you can run loop in template.

Another Best Approach:

If you want to only user specific data in the template app/home.html, so you can simply override the get_queryset inside TaskView in the following way:

views.py

class TaskList(LoginRequiredMixin, ListView):
    model = Task
    template_name = 'app/home.html'
    context_object_name = 'tasks'

    def get_queryset(self):
        qs = super().get_queryset()
        return qs.filter(user=self.request.user)

     #for count you have specifiy in get_context_data
     def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['count'] = self.model.objects.filter(complete=False).count()
        return context   

Then, without changing your code in the template file, you will be able to access user specific data.

Note: It is better to use actual view name as the suffix while working with class based views, so it will be better if you name it as TaskListView instead of only TaskView.

Related