mlemmy mlemmy - 18 days ago 7
Python Question

Iterating through a list twice in django template

In my django template I'm creating two tabs - 'All events' and 'Your events'. In the first tab I want to list all of the events and in the second tab I want to have a filtered list of events (events that user is attending).

To do this I'm looping twice over the same list. The first iteration is all good, but the second doesn't give any output. How can I fix it?

Posting my html template below.

{% block content%}

<ul class="nav nav-tabs" style="padding-top: 60px">
<li class="active"><a data-toggle="tab" href="#allevents">All events</a></li>
<li class=""><a data-toggle="tab" href="#yourevents">Your events</a></li>
</ul>

<div class="container-fluid">
<h1>Event list</h1>
</div>

<div class="tab-content">
<div id="allevents" class="tab-pane fade in active">
{% for event, is_att in event_zip %}

<p> {{ event.name }} </p>
DISPLAY ALL EVENTS HERE - displays correctly

{% endfor %}
</div>

<div id="yourevents" class="tab-pane fade">
{% for event, is_att in event_zip %}

<!-- {% if is_att %} -->
<p> {{ event.name }} </p>
DISPLAY FILTERED EVENTS HERE - doesn't give any output
Does not work even with 'if' statement commented out.
<!-- {% endif %} -->

{% endfor %}
</div>


</div>

{% endblock %}


views.py

@login_required
def events(request):
user = request.user

event_list = Event.objects.all().order_by('date')
is_att = []

for e in event_list:
ev = get_object_or_404(Event, pk=e.id)
if user in ev.list_of_users.all():
is_att.append(True)
else:
is_att.append(False)

event_zip = zip(event_list, is_att)

if request.POST.get('join'):
event_id = request.POST['join']
event_to_join = get_object_or_404(Event, pk=event_id)
event_to_join.list_of_users.add(user)
return HttpResponseRedirect(reverse('events'))

if request.POST.get('leave'):
event_id = request.POST['leave']
event_to_leave = get_object_or_404(Event, pk=event_id)
event_to_leave.list_of_users.remove(user)
return HttpResponseRedirect(reverse('events'))

return render(request, 'events/events.html', {'event_zip' : event_zip})


Event model

class Event(models.Model):
name = models.CharField(max_length=50, blank=False, unique=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateTimeField(blank=False)
place = models.CharField(max_length=50, blank=False)
list_of_users = models.ManyToManyField(User, related_name='user_list', blank=True)

@property
def is_in_future(self):
return self.date > timezone.now()

def __str__(self):
return self.name

Answer

You're using Python 3, where zip is a generator; generators can only be iterated once.

You could fix this by calling list explicitly:

event_zip = list(zip(event_list, is_att))

but really this is already a very inefficient thing to be doing. For every event, you're requesting all associated users and then testing whether the current user is in that list. Instead you should simply do a single separate query to get all the events that the user is associated with:

 user_events = Event.objects.filter(list_of_users=user)

and pass that object separately to the template.

(Note this query could also be spelled user_events = user.user_list.all() - that is unnecessarily confusing because you have used a misnamed related_name on the Event model. The name should be event_list, because it gives you a list of events related to that user; or, just leave that parameter out altogether so you will get the default, event_set.)