URLs and Views

Views are the heart of Django — they process HTTP requests and return HTTP responses. URLs map web addresses to views.

URL Configuration

Django uses URL patterns to route requests. Each URL pattern maps a URL to a view function.

Python
# posts/urls.py (create this file)
from django.urls import path
from . import views

app_name = 'posts'  # Namespacing for {% url 'posts:list' %}

urlpatterns = [
    path('', views.post_list, name='list'),
    path('create/', views.post_create, name='create'),
    path('<slug:slug>/', views.post_detail, name='detail'),
    path('<slug:slug>/edit/', views.post_edit, name='edit'),
    path('<slug:slug>/delete/', views.post_delete, name='delete'),
]
Python
# myblog/urls.py (root URL config)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('django.contrib.auth.urls')),
    path('posts/', include('posts.urls', namespace='posts')),
]

Function-Based Views

Python
# posts/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import Post, Comment
from .forms import PostForm, CommentForm


def post_list(request):
    """Show all published posts."""
    posts = Post.objects.filter(published=True).select_related('author')
    return render(request, 'posts/post_list.html', {'posts': posts})


def post_detail(request, slug):
    """Show a single post with comments."""
    post = get_object_or_404(Post, slug=slug, published=True)
    comments = post.comments.filter(active=True)
    
    if request.method == 'POST':
        comment_form = CommentForm(request.POST)
        if comment_form.is_valid():
            comment = comment_form.save(commit=False)
            comment.post = post
            comment.author = request.user
            comment.save()
            messages.success(request, 'Comment added!')
            return redirect('posts:detail', slug=slug)
    else:
        comment_form = CommentForm()
    
    return render(request, 'posts/post_detail.html', {
        'post': post,
        'comments': comments,
        'comment_form': comment_form,
    })


@login_required
def post_create(request):
    """Create a new post (authenticated users only)."""
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.save()
            form.save_m2m()  # Save tags
            messages.success(request, 'Post created!')
            return redirect('posts:detail', slug=post.slug)
    else:
        form = PostForm()
    return render(request, 'posts/post_form.html', {'form': form, 'action': 'Create'})


@login_required
def post_edit(request, slug):
    """Edit a post (author only)."""
    post = get_object_or_404(Post, slug=slug, author=request.user)
    if request.method == 'POST':
        form = PostForm(request.POST, instance=post)
        if form.is_valid():
            form.save()
            messages.success(request, 'Post updated!')
            return redirect('posts:detail', slug=post.slug)
    else:
        form = PostForm(instance=post)
    return render(request, 'posts/post_form.html', {'form': form, 'action': 'Edit'})


@login_required
def post_delete(request, slug):
    """Delete a post (author only)."""
    post = get_object_or_404(Post, slug=slug, author=request.user)
    if request.method == 'POST':
        post.delete()
        messages.success(request, 'Post deleted!')
        return redirect('posts:list')
    return render(request, 'posts/post_confirm_delete.html', {'post': post})

Class-Based Views (Alternative)

Django provides class-based views for common patterns:

Python
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from .models import Post


class PostListView(ListView):
    model = Post
    template_name = 'posts/post_list.html'
    context_object_name = 'posts'
    paginate_by = 10

    def get_queryset(self):
        return Post.objects.filter(published=True).select_related('author')


class PostDetailView(DetailView):
    model = Post
    template_name = 'posts/post_detail.html'
    slug_field = 'slug'


class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ['title', 'slug', 'body', 'tags']
    template_name = 'posts/post_form.html'

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

FBV vs CBV Comparison

Aspect Function-Based Class-Based
Readability Explicit, easy to follow More abstract
Reuse Via decorators Via mixins and inheritance
Simplicity Simple for simple views Complex for simple views
Testing Test the function directly Use Django's test utilities
When to use Custom logic CRUD patterns

URL Dispatch Flow

Code
Request: GET /posts/my-first-post/

1. Root urls.py matches: 'posts/' → include('posts.urls')
2. posts/urls.py matches: '<slug:slug>/', slug='my-first-post'
3. Calls views.post_detail(request, slug='my-first-post')
4. View returns HttpResponse with rendered HTML
5. Django sends the response back to the browser

💡 Tip: Use select_related('author') on querysets that access ForeignKey fields to avoid N+1 database queries.

Next: Templates and Template Tags