Chapter 9: Django URLs
perfect time to talk about one of the most beautiful and powerful parts of Django: URLs (also called the URL dispatcher or URLconf).
Think of URLs as the front door signs of your website:
- User types or clicks something → browser sends request to your server
- Django looks at the incoming path (e.g. /polls/5/results/)
- It checks your URL patterns like a smart receptionist: “Oh, this matches → hand it to this view function/class!”
Everything starts here. Clean URLs make your site readable, SEO-friendly, and easy to maintain. Django makes this elegant with very little code.
We’ll build everything step-by-step in your existing project (mysite with polls app). I’ll show both project-level and app-level URLs, explain path(), re_path(), include(), naming, parameters, and common real-world patterns in 2026.
1. Where Do URLs Live? Two Levels
| Level | File Location | Purpose | Typical Content Size |
|---|---|---|---|
| Project | mysite/urls.py | Top-level routing, includes app URLs, admin, static/media | Small (5–15 patterns) |
| App | polls/urls.py (you create it) | App-specific routes (e.g. polls list, detail, vote) | Medium (10–30 patterns) |
Best practice in 2026: Put almost all routes in app-level urls.py → keep project urls.py clean and only use include().
2. Create the App-Level URLs File (If Not Already Done)
In polls/ folder, create urls.py if missing:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# polls/urls.py from django.urls import path from . import views # . = current app (polls) app_name = 'polls' # ← super important for namespacing (more later) urlpatterns = [ # We'll fill this soon ] |
3. Connect App URLs to Project (The Include Magic)
Open mysite/urls.py (project level):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
from django.contrib import admin from django.urls import path, include # ← include is key! urlpatterns = [ path('admin/', admin.site.urls), # This line delegates everything starting with /polls/ to polls app path('polls/', include('polls.urls')), # basic include # Optional modern/namespaced style (recommended!) # path('polls/', include(('polls.urls', 'polls'), namespace='polls')), ] |
- include(‘polls.urls’) → “take the rest of the path after /polls/ and match it against polls/urls.py”
- Example: User visits /polls/5/vote/ → Django strips /polls/, looks for 5/vote/ in polls/urls.py
4. Basic path() Examples – The Workhorse (90% of URLs)
path(route, view, name=None) — clean, readable, uses converters like <int:>, <str:>, <slug:>, <uuid:>, <path:>
Add these to 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 |
urlpatterns = [ # 1. Root of the app (/) path('', views.index, name='index'), # → /polls/ # 2. Integer parameter (question ID) path('<int:question_id>/', views.detail, name='detail'), # → /polls/42/ # 3. String / slug parameter path('results/<slug:question_slug>/', views.results, name='results'), # → /polls/results/best-movie-2026/ # 4. Multiple parameters path('archive/<int:year>/<int:month>/', views.archive_by_month, name='archive-month'), # 5. Optional: catch-all remaining path (rare, but useful for CMS) path('pages/<path:subpath>/', views.page_view, name='page'), ] |
Corresponding views in polls/views.py (simple versions):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def index(request): return HttpResponse("Polls home") def detail(request, question_id): return HttpResponse(f"Detail for question #{question_id}") def results(request, question_slug): return HttpResponse(f"Results for slug: {question_slug}") def archive_by_month(request, year, month): return HttpResponse(f"Archive: {year}-{month:02d}") def page_view(request, subpath): return HttpResponse(f"Showing subpage: {subpath}") |
5. Using Named URLs – Never Hardcode Links! (Very Important)
Instead of hardcoding <a href=”/polls/5/”> (breaks if you change prefix), use:
In templates:
|
0 1 2 3 4 5 6 7 8 |
<a href="{% url 'polls:detail' question_id=question.id %}">Vote here</a> <!-- or with slug --> <a href="{% url 'polls:results' question_slug=question.slug %}">See results</a> |
In Python code (views, redirects):
|
0 1 2 3 4 5 6 7 8 9 |
from django.urls import reverse return redirect(reverse('polls:detail', kwargs={'question_id': 7})) # or args= (7,) |
Why app_name = ‘polls’?
- Prevents name clashes if two apps have detail view
- You write polls:detail instead of just detail
6. When to Use re_path() – Regex Power (Only When Needed)
path() is great for 90–95% cases. Use re_path() for complex/old/legacy patterns.
Example in polls/urls.py:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from django.urls import re_path urlpatterns += [ # Legacy year-based URL with regex re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{1,2})/$', views.article_archive, name='article-archive'), # Optional: complex phone number style (rare now) re_path(r'^contact/(?P<phone>(\+91)?[0-9]{10})/$', views.contact_by_phone), ] |
- (?P<name>pattern) → captures named group → passed to view as kwarg
- No converters like <int:> — you write regex yourself
Rule of thumb 2026: Use path() + converters first. Only re_path() for regex you can’t express easily otherwise.
7. Real-World URL Patterns You’ll See in 2026 Projects
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# polls/urls.py – full realistic example app_name = 'polls' urlpatterns = [ path('', views.IndexView.as_view(), name='index'), # CBV path('<int:pk>/', views.QuestionDetailView.as_view(), name='detail'), path('<int:pk>/results/', views.ResultsView.as_view(), name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), # FBV for POST # API-like (if using DRF later) path('api/questions/', views.QuestionListAPI.as_view(), name='api-list'), # Admin-style bulk action (sometimes) path('bulk-delete/', views.bulk_delete_questions, name='bulk-delete'), ] |
8. Common Mistakes & Fixes (Hyderabad Beginner Edition)
-
404 on /polls/ → Forgot include(‘polls.urls’) or path(‘polls/’, …)
-
Reverse for ‘detail’ not found → Forgot app_name = ‘polls’ → use polls:detail
-
Parameter not passed to view → Mismatch: <int:pk> but view has question_id
-
Trailing slash issues → Django auto-redirects /polls → /polls/ (APPEND_SLASH=True default)
-
Order matters → Django matches top-to-bottom → put specific before generic
Bad:
Python01234567path('<int:id>/', views.something),path('special/', views.special),Good:
Python01234567path('special/', views.special),path('<int:id>/', views.something),
Quick Recap Table
| Feature | Syntax / Example | When to Use |
|---|---|---|
| Simple route | path(”, views.index, name=’index’) | Home / list pages |
| Integer param | path(‘<int:pk>/’, …) | IDs, years, counts |
| Slug / string | path(‘<slug:title>/’, …) | SEO-friendly titles |
| Include app URLs | path(‘blog/’, include(‘blog.urls’)) | Modular apps |
| Namespacing | app_name = ‘blog’ + {% url ‘blog:post’ %} | Avoid name clashes |
| Regex (advanced) | re_path(r’^year/(?P<year>\d{4})/$’, …) | Complex / legacy patterns |
Now try this:
- Add 2–3 more patterns to polls/urls.py
- Create simple views for them
- Use {% url ‘polls:detail’ question_id=1 %} in a template (even if empty)
Tell me:
- Did it work? Any 404 / reverse error?
- Want to add templates + dynamic links next?
- Or move to models / views integration with these URLs?
- Or “explain namespaces deeper with multi-app example”?
You’re doing awesome — URLs are the skeleton, and yours is getting strong! 💪🚀 Let’s keep going!
