Display Data
Displaying data from your Django models to the user!
It’s 4:28 PM in Hyderabad on this lovely January 31, 2026 afternoon — perfect time to see your hard work (models, migrations, inserts) finally appear beautifully on the screen.
In this session we’ll focus on all the common and practical ways to display data in Django — from the simplest possible view to full-featured, production-ready patterns used in 2026.
We’ll use our existing polls app (with Question and Choice models).
Goal: Show data in these common scenarios
- List of all/latest questions (index/home page)
- Detail page for one question + its choices
- Results page with vote counts
- Bonus: Filtered list, pagination, admin-style display
Step 1: Quick Reminder — What We Already Have
Models (simplified):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Question(models.Model): question_text = models.CharField(max_length=500) pub_date = models.DateTimeField(default=timezone.now) slug = models.SlugField(max_length=100, unique=True, blank=False) is_active = models.BooleanField(default=True) # ... __str__ ... class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='choices') choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) # ... __str__ ... |
You should have some data already (via admin or shell).
Pattern 1: Function-Based View + Simple Template (Most Beginner-Friendly)
views.py (polls/views.py)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from django.shortcuts import render, get_object_or_404 from .models import Question def index(request): # Get latest 5 active questions, newest first latest_questions = Question.objects.filter( is_active=True ).order_by('-pub_date')[:5] context = { 'latest_questions': latest_questions, 'page_title': 'Latest Polls in Hyderabad - 2026', 'current_time': timezone.now(), } return render(request, 'polls/index.html', context) |
urls.py (polls/urls.py)
|
0 1 2 3 4 5 6 |
path('', views.index, name='index'), |
Template: polls/templates/polls/index.html
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
{% extends "polls/base.html" %} {% block title %}{{ page_title }}{% endblock %} {% block content %} <h1>{{ page_title }}</h1> <p>Showing polls as of {{ current_time|date:"d F Y, H:i" }}</p> {% if latest_questions %} <div class="question-list"> {% for question in latest_questions %} <div class="question-card" style="margin: 1.5rem 0; padding: 1rem; border: 1px solid #ddd; border-radius: 8px;"> <h3> <a href="{% url 'polls:detail' question.slug %}"> {{ question.question_text }} </a> </h3> <small> Published: {{ question.pub_date|date:"d M Y" }} • Category: {{ question.category|default:"General" }} • {% if question.is_active %} <span style="color: green;">Active</span> {% else %} <span style="color: red;">Inactive</span> {% endif %} </small> </div> {% endfor %} </div> {% else %} <p class="no-data" style="color: #666; font-style: italic;"> No active polls available right now. <a href="{% url 'admin:polls_question_add' %}">Create one in admin</a>! </p> {% endif %} {% endblock %} |
→ Result: Clean list of latest questions with links to detail pages using slug (SEO-friendly!)
Pattern 2: Detail View – Show One Question + Choices
views.py
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def detail(request, slug): # Get question by slug (more beautiful URL) question = get_object_or_404(Question, slug=slug, is_active=True) context = { 'question': question, 'choices': question.choices.all(), # using related_name } return render(request, 'polls/detail.html', context) |
urls.py (better than pk!)
|
0 1 2 3 4 5 6 |
path('<slug:slug>/', views.detail, name='detail'), |
Template: polls/templates/polls/detail.html
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
{% extends "polls/base.html" %} {% block title %}{{ question.question_text }}{% endblock %} {% block content %} <h1>{{ question.question_text }}</h1> <p class="meta"> Category: <strong>{{ question.category }}</strong> • Published: {{ question.pub_date|date:"d F Y" }} </p> <h3>Choices:</h3> {% if choices %} <form action="{% url 'polls:vote' question.slug %}" method="post"> {% csrf_token %} <ul style="list-style: none; padding: 0;"> {% for choice in choices %} <li style="margin: 0.8rem 0;"> <label style="display: block; padding: 0.8rem; background: #f8f9fa; border-radius: 6px; cursor: pointer;"> <input type="radio" name="choice" value="{{ choice.id }}" required> {{ choice.choice_text }} {% if choice.votes > 0 %} <span style="color: #006400;">({{ choice.votes }} vote{{ choice.votes|pluralize }})</span> {% endif %} </label> </li> {% endfor %} </ul> <button type="submit" style="margin-top: 1rem; padding: 0.8rem 1.5rem; background: #006400; color: white; border: none; border-radius: 6px;"> Vote Now! </button> </form> {% else %} <p style="color: #666;">No choices added yet.</p> {% endif %} {% endblock %} |
Pattern 3: Class-Based View (Generic) – Less Code, More Power
Many 2026 projects prefer this for lists & details.
views.py
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
from django.views.generic import ListView, DetailView from .models import Question class QuestionListView(ListView): model = Question template_name = 'polls/index.html' context_object_name = 'latest_questions' paginate_by = 10 # ← bonus: pagination! def get_queryset(self): return Question.objects.filter(is_active=True).order_by('-pub_date') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['page_title'] = 'All Active Polls' return context class QuestionDetailView(DetailView): model = Question template_name = 'polls/detail.html' slug_field = 'slug' # use slug instead of pk slug_url_kwarg = 'slug' context_object_name = 'question' def get_queryset(self): return Question.objects.filter(is_active=True) |
urls.py
|
0 1 2 3 4 5 6 7 |
path('', QuestionListView.as_view(), name='index'), path('<slug:slug>/', QuestionDetailView.as_view(), name='detail'), |
→ Much less code, automatic 404 handling, easy pagination (add {% if is_paginated %} in template)
Pattern 4: Display Aggregated Data (Vote Results)
views.py
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
def results(request, slug): question = get_object_or_404(Question, slug=slug) # Simple aggregation example total_votes = question.choices.aggregate(total=Sum('votes'))['total'] or 0 context = { 'question': question, 'total_votes': total_votes, } return render(request, 'polls/results.html', context) |
Template snippet:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<h1>Results: {{ question.question_text }}</h1> <p>Total votes: <strong>{{ total_votes }}</strong></p> {% for choice in question.choices.all %} <div style="margin: 1rem 0;"> <strong>{{ choice.choice_text }}</strong>: {{ choice.votes }} vote{{ choice.votes|pluralize }} {% if total_votes > 0 %} ({{ choice.votes|floatformat:1|div:total_votes|multiply:100 }}%) {% endif %} </div> {% endfor %} |
Quick Comparison Table – Which Pattern to Choose?
| Situation | Recommended Pattern | Why? |
|---|---|---|
| Simple list, few lines | Function-based view | Easy to understand, full control |
| Standard list/detail | Generic ListView / DetailView | Less code, built-in pagination, 404 handling |
| Complex filtering/sorting | Function-based + get_queryset() | Maximum flexibility |
| API-like (JSON) | Django REST framework (later topic) | — |
| Admin-style table | django-tables2 or admin custom | — |
Your Homework / Next Steps
- Implement both function-based and class-based versions of index + detail
- Add some questions with different categories & slugs in admin
- Visit /polls/ and /polls/your-slug-here/
- Try pagination on the list view (add {% load bootstrap_pagination %} or simple links)
Tell me:
- “It worked! Now show me how to add pagination nicely”
- “How to display related choices with vote percentage bar?”
- “I want to show only questions from last 7 days”
- “Got TemplateDoesNotExist or NoReverseMatch — help!”
- Or next big topic: Forms + Voting (POST handling)
You’re now displaying real data like a pro — the app is coming alive! 🌟🚀
Keep going, boss — you’re doing fantastic! 🇮🇳
