Django ListView: How can I create a conditional attribute that's accessible in my template?

Viewed 551

I am using ListView to list some objects from a model. I want to highlight entries in the displayed list where the listed item was created by the current user, and I plan to do this with a small coloured dot (circle) created using CSS. Here's a test case.

# models.py

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

class Foo(models.Model):
    created_by = models.ForeignKey(
        User,
        to_field='username',
        on_delete=models.PROTECT,
        related_name='foos_as_usernames',
        blank=False
    )
    stuff = models.CharField(max_length=128, blank=True)
#views.py

from django.views.generic import ListView

class FooListView(ListView):
    model = Foo

    def get_context_data(self, **kwargs):
        context = super(FooListView, self).get_context_data(**kwargs)

        # Here I want to conditionally set a dot_class attribute based on
        # comparing Foo.created_by with the current user, i.e.
        # if object.created_by == user:
        #     object.dot_class = 'its-me'
        # What do I add here to create the dot_class attribute in the object_list?
        # Is there some other way I can pass a list to the foo_list.html template?

        return context

# foo_list.html

{% load static %}
<!DOCTYPE html>
<html>
<head>
    <title>FooBar</title>
    <style>
        .its-me { height: 8px; width: 8px; background-color: dodgerblue; border-radius: 50%; display: inline-block; }
    </style>
</head>
<body>
    <table>
        <tr>
            <th>&nbsp;</th>
            <th>Stuff</th>
        </tr>
        {% for obj in object_list %}
        <tr>
            <td><span class="{{ obj.dot_class }}"></span></td>
            <td>{{ obj.stuff }}</td>
        </tr>
        {% endfor %}
    </table>
</body>

For a bunch of reasons I need to do this in the CBV rather than annotate objects in the model.

How can I add an attribute to the objects in the context object_list? Alternatively, how can I pass a list to my template?

Thanks and regards...Paul

3 Answers

if you want to send as variable template then try this way:

def get_context_data(self, **kwargs):
    context = super(FooListView, self).get_context_data(**kwargs)
    foo_list = context['foo_list']
    for object in foo_list:
        if object.created_by == self.request.user:
            foo.dot_class = 'its-me'
            foo.save()
    context['foo_list'] = foo_list
    return context

then you can use it directly in template:

<td><span class="{{obj.dot_class}}"></span></td>

Perhaps you can use directly the authentication data that are already available in your template if you want to process specifically the objects that belong to the current connected user: cf django doc authentication-data-in-templates So your template will look like to something like this:

...
<body>
<table>
    <tr>
        <th>&nbsp;</th>
        <th>Stuff</th>
    </tr>
    {% for obj in object_list %}
    <tr>
        <td><span class={% if user.is_authenticated and obj.created_by == user%}"MY_FOO"{% else %}"OTHERS_FOO"{% endif%}></span></td>
        <td>{{ obj.stuff }}</td>
    </tr>
    {% endfor %}
</table>
....
</body>

@Pruthvi Barot's answer was a big help but not quite what I needed. What I realised is that you can't mess with the object_list and you can't iterate over the object_list and another list within the same code block. To overcome that, I create a completely new single list that combines the object_list data with the new data and iterate over that. Here's the code:

# models.py

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

class Foo(models.Model):
    created_by = models.ForeignKey(
        User,
        to_field='username',
        on_delete=models.PROTECT,
        related_name='foos_as_usernames',
        blank=False
    )
    stuff = models.CharField(max_length=128, blank=True)

# views.py

from django.views.generic import ListView

class FooListView(ListView):
    model = Foo

    class NewFoo:
        def __init__(self, created_by, stuff, dot_class):
            self.created_by = created_by
            self.stuff = stuff
            self.dot_class = dot_class

    def get_context_data(self, **kwargs):
        context = super(FooListView, self).get_context_data(**kwargs)

        new_foo_list = []
        foo_list = context['foo_list']

        for foo in foo_list:
            dot_class = ''
            if foo.created_by == self.request.user:
                dot_class = 'its-me'

            new_foo = self.NewFoo(foo.created_by, foo.stuff, dot_class)
            new_foo_list.append(new_foo)

        context['new_foo_list'] = new_foo_list

        return context
# foo_list.html

{% load static %}
<!DOCTYPE html>
<html>
<head>
    <title>FooBar</title>
    <style>
        .its-me { height: 8px; width: 8px; background-color: dodgerblue; border-radius: 50%; display: inline-block; }
    </style>
</head>
<body>
    <table>
        <tr>
            <th>&nbsp;</th>
            <th>Stuff</th>
        </tr>
        {% for obj in new_foo_list %}
        <tr>
            <td><span class="{{ obj.dot_class }}"></span></td>
            <td>{{ obj.stuff }}</td>
        </tr>
        {% endfor %}
    </table>
</body>
Related