2025, Nov 20 09:00
Render the First, Second, and Third Images in Django Templates Using forloop.counter0
Fix Django template loop indexing when rendering images: replace asset.0 with forloop.counter0 to cleanly display the first three images per post in your feed.
Displaying a handful of uploaded images in clearly separated tags sounds trivial—until a Django template starts behaving like a list indexer. If your loop variable isn’t a list, reaching for field.0 or field.1 won’t do anything useful. Here’s a focused walkthrough of the issue and a clean fix using Django’s built-in loop metadata.
Problem setup
We have a model for uploaded files tied to a post, a view that saves the post and classifies uploads as images or videos, and a template that tries to render the first three images in distinct tags.
Data layer:
class MediaItem(models.Model):
entry = models.ForeignKey(Entry, on_delete=models.CASCADE, null=True, blank=True)
blob = models.FileField(upload_to='PostImages/%Y/%m/%d/', null=True, blank=True)
is_image = models.BooleanField(default=False)
is_video = models.BooleanField(default=False)
View logic:
def feed_screen(request):
heading = "For You"
authored_entries = Entry.objects.filter(Q(poster_profile=request.user))
if request.method == "POST":
entry_form = EntryForm(request.POST)
files_form = MediaItemForm(request.POST, request.FILES)
if entry_form.is_valid() and files_form.is_valid():
obj = entry_form.save(commit=False)
obj.poster_profile = request.user
obj.save()
entry_form.save_m2m()
uploaded = request.FILES.getlist('files')
for payload in uploaded:
item = MediaItem.objects.create(entry=obj, blob=payload)
mime, _ = mimetypes.guess_type(item.blob.name)
if mime and mime.startswith('image/'):
item.is_image = True
elif mime and mime.startswith('video/'):
item.is_video = True
item.save()
return redirect('site:foryou_view')
else:
entry_form = EntryForm()
files_form = MediaItemForm()
ctx = {
'page_title': heading,
'poster_profile': authored_entries,
'form': entry_form,
'form_upload': files_form,
}
return render(request, 'foryou.html', ctx)
Template attempt that doesn’t work:
{% if poster_profile %}
{% for person in poster_profile %}
{% if person.mediaitem_set.all|length == 3 %}
{% for asset in person.mediaitem_set.all %}
<!-- Wants first image here -->
{% if asset.0.is_image == True %}
<img src="{{ asset.blob.url }}" alt="img"/>
{% endif %}
<!-- Wants second image here -->
{% if asset.1.is_image == True %}
<img src="{{ asset.blob.url }}" alt="img"/>
{% endif %}
<!-- Wants third image here -->
{% if asset.2.is_image == True %}
<img src="{{ asset.blob.url }}" alt="img"/>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
Why this fails
The inner loop iterates over objects of MediaItem. Each asset is a single object, not a list. Trying to access asset.0, asset.1, or asset.2 assumes the loop variable is indexable like a sequence, which it isn’t. As a result, none of those conditions ever match, and nothing renders.
Django templates already expose loop indices through forloop variables. They are designed exactly for cases like “first”, “second”, and “third” items.
Fix: use forloop counters to target the first three
The correct approach is to keep iterating over the related items and use forloop.counter0 (zero-based index) to distinguish the first three. Then, for each of those, render a dedicated image tag if the item is an image.
{% for asset in person.mediaitem_set.all %}
{% if forloop.counter0 == 0 %}
{% if asset.is_image %}
<img src="{{ asset.blob.url }}" alt="Image 1">
{% endif %}
{% elif forloop.counter0 == 1 %}
{% if asset.is_image %}
<img src="{{ asset.blob.url }}" alt="Image 2">
{% endif %}
{% elif forloop.counter0 == 2 %}
{% if asset.is_image %}
<img src="{{ asset.blob.url }}" alt="Image 3">
{% endif %}
{% else %}
<img src="{{ asset.blob.url }}" alt="Image {{ forloop.counter }}">
{% endif %}
{% endfor %}
This preserves the intent: the first three images get their own dedicated tags, and everything beyond that still renders consistently. The check asset.is_image ensures only images appear in those spots.
Why it matters
Django template logic is intentionally constrained and declarative. Treating objects like lists leads to hard-to-spot failures that produce empty output rather than explicit errors. Knowing about forloop metadata keeps your templates simple, readable, and predictable, especially when you need positional rendering for media galleries, carousels, or feeds.
Conclusion
If you need to place the first, second, and third images in distinct tags, don’t index the loop variable. Lean on forloop.counter0 to branch on position and keep a simple is_image guard inside each branch. This avoids brittle constructs and makes the template behavior obvious at a glance. When in doubt, remember that Django already gives you the indices you need—no list indexing required in the template.