Chapter 2: Django Add Link to Details
Django Add Link to Details
You now have:
- Models with Question + Choice
- Data inserted/updated
- Templates prepared with base.html + inheritance
- List view showing latest questions
The next very important real-world step is: Make each question in the list clickable → link to its detail page
This is one of those small things that separates a “toy project” from something that feels like a real application.
We’re going to do this very carefully, step by step, covering:
- The correct way using {% url %} tag (never hardcode!)
- Using slug vs pk in URLs
- Handling the link in list view
- Updating detail view & URL pattern
- Common mistakes & debugging tips
Let’s build it together.
Step 1: Decide URL Style for Detail Page
Two common choices in 2026:
| Style | URL example | Pros | Cons | When to use |
|---|---|---|---|---|
| Numeric ID (pk) | /polls/42/ | Simple, short | Not SEO-friendly, ugly | Internal tools, admin-like apps |
| Slug (readable) | /polls/best-biryani-hyderabad/ | Beautiful, SEO, shareable | Needs unique slug field | Public-facing sites (most cases) |
Recommendation for your polls app: Use slug — it looks professional and teaches you a very common pattern.
(If you don’t have slug field yet → add it now, see previous “Update Model” lesson)
Step 2: Update URL Pattern for Detail (Use Slug)
In polls/urls.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.urls import path from . import views app_name = 'polls' # ← must have for namespaced urls urlpatterns = [ # List / home path('', views.index, name='index'), # Detail – using slug instead of <int:pk> path('<slug:slug>/', views.detail, name='detail'), # Later: vote and results # path('<slug:slug>/vote/', views.vote, name='vote'), # path('<slug:slug>/results/', views.results, name='results'), ] |
- <slug:slug> → built-in converter: letters, numbers, hyphens, underscores
- name=’detail’ → we’ll use this in templates
Step 3: Update Detail View to Use Slug
In 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 22 23 |
from django.shortcuts import render, get_object_or_404 from .models import Question def detail(request, slug): # Lookup by slug + only active questions question = get_object_or_404( Question, slug=slug, is_active=True ) context = { 'question': question, 'choices': question.choices.all().order_by('id'), # or by votes later 'total_votes': question.choices.aggregate(total=Sum('votes'))['total'] or 0, } return render(request, 'polls/detail.html', context) |
Step 4: Add the Link in the List Template (The Main Goal!)
Open polls/templates/polls/index.html
Find the loop where you display questions (inside {% for question in latest_questions %}):
Before (probably plain text):
|
0 1 2 3 4 5 6 |
<h3>{{ question.question_text }}</h3> |
After (with proper link):
|
0 1 2 3 4 5 6 7 8 9 10 11 |
<h3> <a href="{% url 'polls:detail' slug=question.slug %}" style="color: var(--primary); text-decoration: none;"> {{ question.question_text }} </a> </h3> |
Or more beautiful card style (recommended):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<div class="question-card" style="background:white; padding:1.5rem; border-radius:8px; box-shadow:0 2px 10px rgba(0,0,0,0.08);"> <h3 style="margin-top:0;"> <a href="{% url 'polls:detail' slug=question.slug %}" style="color: #006400; text-decoration: none;"> {{ question.question_text }} </a> </h3> <div style="color:#666; font-size:0.95rem; margin:0.8rem 0;"> Category: {{ question.category|title }} • Published {{ question.pub_date|timesince }} ago </div> <a href="{% url 'polls:detail' slug=question.slug %}" class="btn" style="display:inline-block; padding:0.6rem 1.2rem; background:#006400; color:white; border-radius:6px; text-decoration:none;"> View & Vote → </a> </div> |
Key points in the link:
- {% url ‘polls:detail’ slug=question.slug %}
- polls: → namespace from app_name
- detail → name from urls.py
- slug=question.slug → keyword argument matching <slug:slug> in path
Never write:
|
0 1 2 3 4 5 6 |
<a href="/polls/{{ question.slug }}/"> <!-- BAD – breaks if URL prefix changes --> |
or
|
0 1 2 3 4 5 6 |
<a href="/polls/{{ question.id }}/"> <!-- BAD – not readable, not SEO --> |
Step 5: Test It Live
-
Make sure you have at least 2–3 questions with unique slugs (in admin: edit question → slug should auto-fill if you overrode .save())
-
Run server:
Bash0123456python manage.py runserver -
Visit http://127.0.0.1:8000/polls/
-
Click any question title or “View & Vote” button
→ You should land on the detail page showing that specific question + choices
Step 6: Debugging Common Problems
| Problem | Error Message / Symptom | Fix |
|---|---|---|
| NoReverseMatch | Reverse for ‘detail’ not found | Add app_name = ‘polls’ in polls/urls.py |
| slug not passed | url() missing 1 required positional argument | Use keyword slug=question.slug (not positional) |
| 404 on click | Question matching query does not exist | Check slug is unique & populated; use get_object_or_404 |
| Link shows /polls/None/ | question.slug is blank | Populate slugs (data migration or .save() override) |
| Link works but shows wrong question | Multiple questions same slug | Add unique=True + run migration + fix duplicates |
Bonus: Make Slug Auto-Unique (Pro Touch)
In models.py → improve .save():
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def save(self, *args, **kwargs): if not self.slug: base_slug = slugify(self.question_text)[:90] self.slug = base_slug counter = 1 while Question.objects.filter(slug=self.slug).exclude(pk=self.pk).exists(): self.slug = f"{base_slug}-{counter}" counter += 1 super().save(*args, **kwargs) |
→ Prevents duplicates automatically (e.g. “best-biryani-2”)
Your Quick Homework
- Add the <a href=”{% url ‘polls:detail’ slug=question.slug %}”> in your index template
- Create 3 questions with different slugs
- Click around — make sure detail page loads correct data
- Try changing one slug in admin → refresh list → link updates automatically
Tell me next:
- “Links work! Now show me how to add voting (POST form + F() update)”
- “How to make slug links look nicer (remove ID completely)?”
- “I want breadcrumbs or back button to list”
- “Got NoReverseMatch or 404 – here’s screenshot/error”
- Or next topic: Django Forms / ModelForm
You’re now connecting list → detail like a real web app — super proud of your progress! Let’s keep going — this is getting exciting 🚀🇮🇳
