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>