Views & URLs
Reviewed & published by Brayan K
In Django, a view is a Python function (or class) that takes a web request and returns a web response, and a URL configuration (urls.py) is the routing table that maps each URL path to the view that should handle it.
Part of the free Django course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll write your first view that returns an HttpResponse, wire it up in urls.py with the path() function, and learn how to capture values straight from the URL and pass them into your view.
What You'll Learn in This Lesson
- What a view is and how it returns a response
- Writing a function-based view with HttpResponse
- How urls.py routes paths to views with path()
- Naming URL patterns with the name argument
- Capturing URL values with path converters
- Passing captured values into your view function
1 Writing a Function-Based View
A view is the simplest building block in Django. It is a plain Python function that accepts a request object as its first argument and returns a response. The most basic response is an HttpResponse containing some text or HTML.
Views live in a file called views.py inside your app. Here is a minimal one:
# blog/views.py
from django.http import HttpResponse
def about(request):
return HttpResponse("This is the about page.")The function receives the incoming request, builds an HttpResponse, and returns it. Django sends that response straight back to the browser. Every view must return a response object — returning nothing is an error.
# A Django view is just a function that takes a request and returns a
# response. We'll fake HttpResponse with a simple class so you can run it.
class HttpResponse:
def __init__(self, content):
self.content = content
def __str__(self):
return f"HttpResponse: {self.content}"
def about(request):
"""A view takes a request and returns a response."""
return HttpResponse("This is the about page.")
# Django builds a request and calls your view. We'll simulate that:
fake_request = {"path": "/about/", "method": "GET"}
response = about(fake_request)
print(response)
print("Path requested:", fake_request["path"])
# In real Django you import HttpResponse from django.http — same idea.
# ✅ Expected output:
# HttpResponse: This is the about page.
# Path requested: /about/2 Routing with urls.py and path()
A view does nothing on its own — Django needs to know which URL should trigger it. That mapping lives in urls.py, your project's routing table. You build it with the path() function from django.urls.
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('about/', views.about, name='about'),
]The path() function takes three useful parts:
- route — the URL string to match, here 'about/'.
- view — the view function to run, here views.about.
- name — a label you can reference later, here name='about'.
So path('about/', views.about, name='about') reads as: "when a browser requests /about/, run the about view, and call this route 'about'."
# urls.py is really just a list that maps a URL string to a view.
# Here we simulate Django's URL resolver in plain Python.
def about(request):
return "This is the about page."
def home(request):
return "Welcome home!"
# Each "path" pairs a route with a view and a name.
urlpatterns = [
{"route": "about/", "view": about, "name": "about"},
{"route": "", "view": home, "name": "home"},
]
def resolve(requested_path):
"""Find the view whose route matches the requested path."""
for pattern in urlpatterns:
if pattern["route"] == requested_path:
return pattern["view"]({"path": requested_path})
return "404 Not Found"
print(resolve("about/")) # runs the about view
print(resolve("")) # runs the home view
print(resolve("missing/")) # no pattern -> 404
# ✅ Expected output:
# This is the about page.
# Welcome home!
# 404 Not Found3 Capturing URL Parameters
Real apps need dynamic URLs — a blog post page like /posts/5/ shouldn't need a separate view for every post. Django captures part of the URL using a path converter written in angle brackets, then passes the captured value into your view as an argument.
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('posts/<int:post_id>/', views.post_detail, name='post_detail'),
]
# blog/views.py
from django.http import HttpResponse
def post_detail(request, post_id):
return HttpResponse(f"You are viewing post #{post_id}")The converter <int:post_id> has two parts: int says "match a whole number," and post_id is the name Django uses when it passes the value to your view. Visiting /posts/5/ calls post_detail(request, post_id=5).
- <int:post_id> — matches a number like 5 or 42.
- <str:username> — matches text up to the next slash.
- <slug:slug> — matches a hyphenated label like my-first-post.
# When a URL has <int:post_id>, Django pulls the number out of the path
# and passes it to your view. Let's simulate that capture step.
def post_detail(request, post_id):
return f"You are viewing post #{post_id}"
def resolve_post(requested_path):
"""Pretend route is 'posts/<int:post_id>/' and extract the id."""
parts = requested_path.strip("/").split("/")
# parts looks like ["posts", "5"]
if len(parts) == 2 and parts[0] == "posts" and parts[1].isdigit():
post_id = int(parts[1]) # the captured <int:post_id>
return post_detail({"path": requested_path}, post_id=post_id)
return "404 Not Found"
print(resolve_post("/posts/5/")) # post_id captured as 5
print(resolve_post("/posts/42/")) # post_id captured as 42
print(resolve_post("/posts/abc/")) # not an int -> 404
# ✅ Expected output:
# You are viewing post #5
# You are viewing post #42
# 404 Not Found🎯 Your Turn: Finish the View
Fill in the blank so the view returns a response that greets the captured username.
# 🎯 YOUR TURN: replace ___ with the parameter name "username"
# so the view uses the value captured from the URL.
def greet_user(request, username):
# The URL pattern was: 'users/<str:username>/'
return f"Hello, {___}!"
print(greet_user({"path": "/users/alice/"}, "alice"))
# EXPECTED OUTPUT:
# Hello, alice!Common Errors (And How to Fix Them)
❌ The view didn't return an HttpResponse object
Your view ran but forgot to return a response, so it returned None.
✅ Fix: make sure every path through the view ends with return HttpResponse(...) or return render(...).
❌ Page not found (404) for a URL you just wrote
The path isn't registered in urlpatterns, or the route string doesn't match (a missing trailing slash is a common cause).
✅ Fix: add the path(...) entry to urls.py and check the route string matches exactly.
❌ TypeError: post_detail() got an unexpected keyword argument
The converter name in the URL doesn't match the argument name in your view.
✅ Fix: make <int:post_id> and the view's post_id parameter use the exact same name.
📋 Quick Reference
| Code | What It Does |
|---|---|
| def about(request): | Define a function-based view |
| HttpResponse("Hi") | Build a basic text/HTML response |
| path("about/", views.about, name="about") | Map a URL to a view and name it |
| path("posts/<int:post_id>/", ...) | Capture a number from the URL |
🏆 Mini Challenge: A Tiny URL Resolver
Build a mini routing table that maps two paths to two views, then resolve a couple of requests through it — just like Django does.
# 🏆 Mini Challenge: build a tiny URL resolver with two routes.
def home(request):
return "Home page"
def product_detail(request, product_id):
return f"Product #{product_id}"
urlpatterns = [
{"route": "", "view": home},
# 'products/<int:product_id>/' captures the trailing number
{"route": "products/<int:product_id>/", "view": product_detail},
]
def resolve(path):
parts = path.strip("/").split("/")
if path.strip("/") == "":
return home({"path": path})
if len(parts) == 2 and parts[0] == "products" and parts[1].isdigit():
return product_detail({"path": path}, int(parts[1]))
return "404 Not Found"
print(resolve("/"))
print(resolve("/products/7/"))
print(resolve("/nope/"))
# EXPECTED:
# Home page
# Product #7
# 404 Not Found❓ Frequently Asked Questions
Lesson complete — your URLs now reach your views!
You can write a function-based view that returns an HttpResponse, register it in urls.py with the path() function, name your routes, and capture dynamic values from the URL with path converters like <int:post_id>.
Practice quiz
What is a function-based view in Django?
- A routing rule in urls.py
- A Python function that takes a request and returns a response
- A template that renders HTML
- A database model class
Answer: A Python function that takes a request and returns a response. A view is a function that receives a request object and returns a response such as an HttpResponse.
What is the first argument every function-based view receives?
- The response object
- The URL string
- The context dictionary
- The request object
Answer: The request object. A view's first parameter is always the incoming request object.
Which class builds a basic text or HTML response?
- HttpResponse
- HttpRequest
- JsonRequest
- TemplateResponse
Answer: HttpResponse. HttpResponse('...') from django.http builds a simple text/HTML response.
What happens if a view returns None instead of a response?
- Django returns a blank page
- The previous response is reused
- Django raises 'The view didn't return an HttpResponse object.'
- Django returns a 200 with empty body
Answer: Django raises 'The view didn't return an HttpResponse object.'. Every view must return a response object; returning None raises that error.
Which function maps a URL to a view in urls.py?
- route()
- path()
- url_map()
- connect()
Answer: path(). path() from django.urls maps a route string to a view, with an optional name.
What does name='about' in a path() call let you do?
- Reverse the URL elsewhere with {% url 'about' %} or reverse('about')
- Cache the view's output
- Restrict the view to GET requests
- Automatically create a template named about.html
Answer: Reverse the URL elsewhere with {% url 'about' %} or reverse('about'). Naming a URL lets you build it by name so changing the path later does not break links.
Which path converter matches a whole number from the URL?
- <str:id>
- <slug:id>
- <num:id>
- <int:post_id>
Answer: <int:post_id>. <int:post_id> matches a whole number and passes it to the view as post_id.
Given path('posts/<int:post_id>/', ...), how is the view called for /posts/5/?
- post_detail(5)
- post_detail(request)
- post_detail(request, post_id=5)
- post_detail(post_id=5, request)
Answer: post_detail(request, post_id=5). The captured value is passed as a keyword argument: post_detail(request, post_id=5).
Which converter matches a hyphenated label like my-first-post?
- <slug:slug>
- <int:slug>
- <str:slug>
- <path:slug>
Answer: <slug:slug>. The slug converter matches hyphenated, lowercase labels like my-first-post.
A TypeError about an unexpected keyword argument from a captured value usually means:
- The view forgot to import HttpResponse
- The URL route has a trailing slash
- The converter name does not match the view's parameter name
- The path is not in urlpatterns
Answer: The converter name does not match the view's parameter name. The name in <int:post_id> must match the view's parameter name, or Django raises a TypeError.
Continue this course
- Previous: Projects vs Apps
- Next: Templates — Render dynamic HTML with the Django template language and context data
- Quick reference: Django cheat sheet › Views & URLs
Frequently asked questions
What is the difference between a view and a URL in Django?
A view is a Python function (or class) that contains your logic: it receives a request and returns a response, such as some HTML or JSON. A URL pattern, defined in urls.py, is a routing rule that says 'when a browser asks for this path, run that view.' The URL is the address; the view is what happens when someone visits it. You need both: a view does nothing until a URL points to it.
Why does my view need to return an HttpResponse?
Django's request/response cycle expects every view to hand back a response object so it knows what to send to the browser. If your view returns None or forgets the return statement, Django raises 'The view didn't return an HttpResponse object.' Even a simple view must return something like HttpResponse('Hello') or a helper such as render() or JsonResponse() that produces a response under the hood.
What does name='about' do in a path() call?
The name argument gives a URL pattern a unique label you can refer to elsewhere instead of hard-coding the path. With name='about' you can build the URL in templates using {% url 'about' %} or in Python with reverse('about'). If you later change the path from 'about/' to 'about-us/', every link built from the name updates automatically, so naming your URLs keeps your project maintainable.
How do I capture a value from the URL, like a post id?
Use a path converter inside angle brackets, for example 'posts/<int:post_id>/'. The <int:post_id> part matches a whole number in the URL and passes it to your view as a keyword argument named post_id. Common converters include int for numbers, str for text without slashes, and slug for hyphenated labels. The name after the colon must match the parameter name in your view function.