Home > Software engineering >  Accessing items in a list through template tags in Django template tags
Accessing items in a list through template tags in Django template tags

Time:05-06

Template tag screenshot and possible solution options

First time, posting to stack-overflow. Please excuse if formatting is not ideal.

html

<tbody>
       {% for list in todolist %} 
          <tr>
            <td>
               <a href="{% url 'todo_detail' list.pk %}">{{ list.name }}</a>  
                  </td>
                   <td>
                   {{ list.items.all|length }}
                    </td>
                    <td>
    {% comment %}need this to be allCompleted[list.id - 1]
 #allCompleted.0 works. allCompleted[0] or allCompleted['0'] does not.{% endcomment %}

                       {% if allCompleted == True %}  

                       {{ allCompleted|getindex:list.id }}

                        Yes

                        {% else %}

                        No  

                        {% endif %}
                    </td>
                </tr> 
               {% endfor %} 
            </tbody>

Views.py:

class TodoListListView(ListView):
model = TodoList
context_object_name = "todolist"
template_name = "todos/list.html"

def get_context_data(self, **kwargs): 
    context = super().get_context_data(**kwargs) 
    allCompletedList = [] 
    for list in TodoList.objects.all(): 
        
        allCompleted = True
        for item in list.items.all(): 
            if item.is_completed == False: 
                allCompleted = False 
        allCompletedList.append(allCompleted)
        
        context['allCompleted'] = allCompletedList

    print ('context: ', context)
    return context 

Models.py

class TodoList(models.Model):
    name = models.CharField(max_length=100, blank=False)
    created_on = models.DateTimeField(auto_now_add=True)

    def __str__(self): 
        return self.name 

class TodoItem(models.Model):
    task = models.CharField(max_length=100)
    due_date = models.DateTimeField(null=True, blank=True)
    is_completed = models.BooleanField(default=False)
    list = models.ForeignKey("Todolist", related_name="items", on_delete=models.CASCADE)

    def __str__(self): 
        return self.task 

When printing context: I get 'allCompleted': [False, True] This is accurate as I have some items in housing chores not completed but I triple checked to make sure all my coding projects are completed.

As seen from the HTML screenshot, I need something like:

{{ allCompleted[list.id - 1] }} to match with the corresponding list in each row.

But it seems Django does not like that. I've tried many combos like allCompleted['list.id-1']. Strangely, allCompleted.0 = False but allCompleted[0] gets a parse error. I have also tried to create a custom template tag in my app/templatetag folder under a file I made (getindex.py)

from django import template
from todos.models import TodoItem, TodoList

register = template.Library()

def getindex(lst, idx):
   return lst[idx]

register.filter(getindex)

For my template tag, I did {{ allCompleted|getindex:list.id-1 }} and it says getindex is not a valid filter so maybe I am registering it incorrectly?

If there is no way to access allCompleted[list.id - 1], I thought of other solutions explained in my HTMl screenshot.

CodePudding user response:

Instead of using template tags for this purpose, it is best to give to the template data in the form it can easily use. The most efficient way to do this would be to leave the task to the database itself and write a query that will give you allCompleted as a column in the result. You can use Exists() subqueries to do this:

from django.db.models import Exists, OuterRef


class TodoListListView(ListView):
    model = TodoList
    context_object_name = "todolist"
    template_name = "todos/list.html"
    
    def get_queryset(self):
        queryset = super().get_queryset()
        subquery = TodoItem.objects.filter(
            list=OuterRef('pk'),
            is_completed=False
        )
        queryset = queryset.annotate(all_completed=~Exists(subquery))
        return queryset

Then in your template you can simply write {{ list.all_completed }}:

<tbody>
   {% for list in todolist %} 
   <tr>
      <td>
         <a href="{% url 'todo_detail' list.pk %}">{{ list.name }}</a>  
      </td>
      <td>
         {{ list.items.all|length }}
      </td>
      <td>
         {% if list.all_completed == True %}
         Yes
         {% else %}
         No  
         {% endif %}
      </td>
   </tr>
   {% endfor %} 
</tbody>
  • Related