Django 视图与URL配置

🛣️ Django 请求处理流程

视图是Django应用的核心,负责处理Web请求并返回响应。URL配置则将URL映射到相应的视图函数。

Django MTV 架构中的视图

在Django的MTV(Model-Template-View)架构中:

  • 视图 (View) - 处理业务逻辑,连接模型和模板
  • URL配置 (URLconf) - 定义URL模式到视图的映射
  • 请求-响应循环 - Django处理HTTP请求的完整流程

Django 请求处理流程

URL 请求

用户访问URL

URL 路由

URLconf 匹配

视图处理

执行视图函数

返回响应

生成HTTP响应

📝
函数视图

简单的Python函数,接收请求返回响应

🏗️
类视图

基于类的视图,提供更好的代码组织和复用

🔧
内置视图

Django提供的通用视图,处理常见场景

URL配置基础

🔗 URLconf:URL调度器

URL配置(URLconf)是一个Python模块,它定义了URL模式到视图函数的映射关系。

基本的URL配置

项目根URL配置示例:

# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
from django.http import HttpResponse

def home_view(request):
return HttpResponse("欢迎来到首页!")

urlpatterns = [
path('admin/', admin.site.urls),
path('', home_view, name='home'),
path('blog/', include('blog.urls')),
]

应用级别的URL配置

在应用中创建独立的URL配置:

# blog/urls.py
from django.urls import path
from . import views

urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<int:post_id>/', views.post_detail, name='post_detail'),
path('category/<slug:category_slug>/', views.category_posts, name='category_posts'),
]

URL 模式解析

理解URL模式的各个部分:

blog/ post/ <int: post_id >/
静态部分
固定的URL路径
转换器
参数类型定义
变量名
视图参数名称
分隔符
URL路径分隔

函数视图

基本函数视图
from django.http import HttpResponse

def hello_world(request):
return HttpResponse("Hello, World!")

def current_time(request):
from datetime import datetime
now = datetime.now()
return HttpResponse(f"当前时间: {now}")
带参数的函数视图
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from .models import Post

def post_detail(request, post_id):
# 获取文章对象,如果不存在返回404
post = get_object_or_404(Post, id=post_id)

# 渲染模板
return render(request, 'blog/post_detail.html', {
    'post': post
})

def user_profile(request, username):
return HttpResponse(f"用户 {username} 的个人资料")

处理不同的HTTP方法

在函数视图中处理GET、POST等HTTP方法:

from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import PostForm
from .models import Post

def post_create(request):
if request.method == 'POST':
    # 处理表单提交
    form = PostForm(request.POST)
    if form.is_valid():
        post = form.save(commit=False)
        post.author = request.user
        post.save()
        messages.success(request, '文章创建成功!')
        return redirect('post_detail', post_id=post.id)
else:
    # 显示空表单
    form = PostForm()

return render(request, 'blog/post_form.html', {
    'form': form,
    'title': '创建新文章'
})

def post_update(request, post_id):
post = get_object_or_404(Post, id=post_id)

if request.method == 'POST':
    form = PostForm(request.POST, instance=post)
    if form.is_valid():
        form.save()
        messages.success(request, '文章更新成功!')
        return redirect('post_detail', post_id=post.id)
else:
    form = PostForm(instance=post)

return render(request, 'blog/post_form.html', {
    'form': form,
    'title': '编辑文章',
    'post': post
})

路径转换器和参数

内置路径转换器

Django提供的内置路径转换器:

from django.urls import path

urlpatterns = [
# str - 匹配任何非空字符串(默认)
path('user/<str:username>/', views.user_profile),

# int - 匹配正整数
path('post/<int:post_id>/', views.post_detail),

# slug - 匹配字母、数字、连字符、下划线
path('category/<slug:category_slug>/', views.category_posts),

# uuid - 匹配UUID格式字符串
path('file/<uuid:file_id>/', views.file_download),

# path - 匹配任何非空字符串,包括路径分隔符
path('docs/<path:doc_path>/', views.serve_document),
]

查询参数和URL参数

处理URL路径参数和查询字符串:

from django.shortcuts import render
from .models import Post

def post_list(request):
# 获取查询参数
category = request.GET.get('category', '')
page = request.GET.get('page', 1)
search = request.GET.get('q', '')

# 构建查询集
posts = Post.objects.all()

if category:
    posts = posts.filter(category__slug=category)

if search:
    posts = posts.filter(title__icontains=search)

# 分页逻辑...

return render(request, 'blog/post_list.html', {
    'posts': posts,
    'current_category': category,
    'search_query': search
})

# 对应的URL示例:
# /blog/?category=django&q=views&page=2

自定义路径转换器

创建自定义的路径转换器来处理特殊格式:

# 在 converters.py 中定义自定义转换器
class YearMonthConverter:
regex = r'[0-9]{4}-[0-9]{2}'  # 匹配 YYYY-MM 格式

def to_python(self, value):
    # 将URL参数转换为Python对象
    year, month = map(int, value.split('-'))
    return {'year': year, 'month': month}

def to_url(self, value):
    # 将Python对象转换为URL字符串
    return f"{value['year']}-{value['month']:02d}"

# 注册自定义转换器
from django.urls import register_converter
register_converter(YearMonthConverter, 'yearmonth')

# 使用自定义转换器
urlpatterns = [
path('archive/<yearmonth:period>/', views.monthly_archive),
]

类视图

🏗️ 类视图的继承层次

Django的类视图基于面向对象设计,提供了清晰的继承结构。

View (基础视图类)

所有类视图的基类,定义了基本的请求处理方法。

from django.views import View
from django.http import HttpResponse

class MyView(View):
def get(self, request, *args, **kwargs):
    return HttpResponse("GET请求")

def post(self, request, *args, **kwargs):
    return HttpResponse("POST请求")
TemplateView (模板视图)

用于显示模板的通用视图。

from django.views.generic import TemplateView

class HomeView(TemplateView):
template_name = 'home.html'

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['latest_posts'] = Post.objects.all()[:5]
    return context
ListView (列表视图)

用于显示对象列表的通用视图。

from django.views.generic import ListView
from .models import Post

class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
ordering = ['-created_at']

def get_queryset(self):
    # 自定义查询集
    queryset = super().get_queryset()
    category_slug = self.kwargs.get('category_slug')
    if category_slug:
        queryset = queryset.filter(category__slug=category_slug)
    return queryset
DetailView (详情视图)

用于显示单个对象详情的通用视图。

from django.views.generic import DetailView
from .models import Post

class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'

def get_object(self, queryset=None):
    # 自定义对象获取逻辑
    obj = super().get_object(queryset)
    # 增加浏览次数
    obj.view_count += 1
    obj.save()
    return obj

视图装饰器

🎨 使用装饰器增强视图功能

装饰器可以方便地为视图添加额外功能,如权限检查、缓存、登录要求等。

常用内置装饰器

from django.contrib.auth.decorators import login_required, permission_required
from django.views.decorators.http import require_http_methods
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie

# 要求用户登录
@login_required
def my_profile(request):
return render(request, 'users/profile.html')

# 要求特定权限
@permission_required('blog.add_post')
def create_post(request):
# 创建文章逻辑
pass

# 限制HTTP方法
@require_http_methods(["GET", "POST"])
def contact_form(request):
if request.method == 'POST':
    # 处理表单提交
    pass
# 显示表单
return render(request, 'contact.html')

# 缓存视图
@cache_page(60 * 15)  # 缓存15分钟
@vary_on_cookie
def expensive_view(request):
# 计算密集型操作
return render(request, 'expensive.html')

# 组合使用装饰器
@login_required
@permission_required('blog.change_post')
@require_http_methods(["GET", "POST"])
def post_edit(request, post_id):
# 编辑文章逻辑
pass

自定义装饰器

创建自定义装饰器来处理特定需求:

from functools import wraps
from django.http import HttpResponseForbidden

def staff_required(view_func):
"""要求用户必须是员工"""
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
    if not request.user.is_staff:
        return HttpResponseForbidden("需要员工权限")
    return view_func(request, *args, **kwargs)
return _wrapped_view

def superuser_required(view_func):
"""要求用户必须是超级用户"""
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
    if not request.user.is_superuser:
        return HttpResponseForbidden("需要超级用户权限")
    return view_func(request, *args, **kwargs)
return _wrapped_view

# 使用自定义装饰器
@staff_required
def staff_dashboard(request):
return render(request, 'staff/dashboard.html')

视图响应类型

Django 支持多种响应类型

📄
HttpResponse

基本的HTTP响应,可以返回任何内容。

from django.http import HttpResponse

def text_response(request):
return HttpResponse("纯文本响应")

def json_response(request):
import json
data = {'status': 'success', 'message': '操作成功'}
return HttpResponse(
    json.dumps(data),
    content_type='application/json'
)
🎨
render() - 模板渲染

渲染模板并返回HTML响应。

from django.shortcuts import render

def template_response(request):
context = {
    'title': '页面标题',
    'users': User.objects.all(),
    'current_time': timezone.now()
}
return render(request, 'my_template.html', context)
🔄
redirect() - 重定向

重定向到其他URL。

from django.shortcuts import redirect

def redirect_examples(request):
# 重定向到命名URL
return redirect('post_list')

# 重定向到具体URL
return redirect('/blog/')

# 重定向到带参数的命名URL
return redirect('post_detail', post_id=1)

# 重定向到外部URL
return redirect('https://example.com')
📁
JsonResponse

返回JSON格式的响应。

from django.http import JsonResponse

def api_example(request):
data = {
    'posts': [
        {'id': 1, 'title': '文章1'},
        {'id': 2, 'title': '文章2'}
    ],
    'count': 2
}
return JsonResponse(data)

def api_with_status(request):
if request.method != 'POST':
    return JsonResponse(
        {'error': '方法不允许'},
        status=405
    )
# 处理POST请求
return JsonResponse({'success': True})

URL命名和命名空间

🔤 URL命名和反向解析

为URL模式命名可以让你在代码中引用它们,而不需要硬编码URL。

# urls.py - 定义命名URL
from django.urls import path
from . import views

urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<int:post_id>/', views.post_detail, name='post_detail'),
path('category/<slug:category_slug>/', views.category_posts, name='category_posts'),
path('create/', views.post_create, name='post_create'),
]
# 在视图和模板中使用反向解析
from django.urls import reverse
from django.shortcuts import redirect

def some_view(request):
# 在视图中使用反向解析
url = reverse('post_list')  # 返回 '/blog/'
detail_url = reverse('post_detail', args=[1])  # 返回 '/blog/post/1/'
category_url = reverse('category_posts', kwargs={'category_slug': 'django'})

# 重定向到命名URL
return redirect('post_create')

# 在模板中使用
# <a href="{% url 'post_list' %}">文章列表</a>
# <a href="{% url 'post_detail' post.id %}">文章详情</a>
# 项目级URL配置 - 包含应用URL并指定命名空间
from django.urls import path, include

urlpatterns = [
path('blog/', include(('blog.urls', 'blog'), namespace='blog')),
path('users/', include(('users.urls', 'users'), namespace='users')),
]

# 在代码中使用带命名空间的URL
reverse('blog:post_list')  # 返回 '/blog/'
reverse('blog:post_detail', args=[1])  # 返回 '/blog/post/1/'

# 在模板中使用
# <a href="{% url 'blog:post_list' %}">博客首页</a>

错误处理

🚨 正确处理HTTP错误

Django提供了内置的错误处理机制,同时你也可以自定义错误页面。

内置错误视图

# 在项目URL配置中(可选)
from django.urls import path
from django.conf.urls import handler404, handler500

urlpatterns = [
# ... 其他URL模式 ...
]

# 自定义错误处理视图
handler404 = 'myapp.views.custom_page_not_found'
handler500 = 'myapp.views.custom_server_error'
handler403 = 'myapp.views.custom_permission_denied'
handler400 = 'myapp.views.custom_bad_request'

# 在视图中手动触发错误
from django.http import Http404
from django.shortcuts import get_object_or_404

def post_detail(request, post_id):
# 方法1:使用get_object_or_404
post = get_object_or_404(Post, id=post_id)

# 方法2:手动抛出Http404
try:
    post = Post.objects.get(id=post_id)
except Post.DoesNotExist:
    raise Http404("文章不存在")

return render(request, 'blog/post_detail.html', {'post': post})

自定义错误页面

# myapp/views.py
from django.shortcuts import render

def custom_page_not_found(request, exception):
return render(request, '404.html', status=404)

def custom_server_error(request):
return render(request, '500.html', status=500)

def custom_permission_denied(request, exception):
return render(request, '403.html', status=403)

def custom_bad_request(request, exception):
return render(request, '400.html', status=400)

# 创建对应的模板文件
# templates/404.html, templates/500.html 等

交互式URL测试器

🔍 测试URL模式匹配

输入URL模式和测试URL,查看匹配结果和提取的参数:

URL 模式
示例模式:
blog/
blog/post/<int:post_id>/
category/<slug:category_slug>/
user/<str:username>/posts/
archive/<int:year>/<int:month>/
测试 URL
输入URL模式和测试URL,然后点击"测试匹配"按钮。

视图与URL配置最佳实践

📝
使用命名URL

避免硬编码URL,使用反向解析

🏗️
合理选择视图类型

简单逻辑用函数视图,复杂功能用类视图

🔐
实施权限控制

使用装饰器保护敏感视图

📊
合理组织URL结构

使用RESTful风格的URL设计

🚨
处理错误情况

提供有意义的错误页面和消息

🧪
编写可测试的视图

保持视图简洁,便于单元测试

完整的博客应用示例

# blog/urls.py
from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
path('', views.PostListView.as_view(), name='post_list'),
path('post/<int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
path('create/', views.PostCreateView.as_view(), name='post_create'),
path('post/<int:pk>/edit/', views.PostUpdateView.as_view(), name='post_edit'),
path('post/<int:pk>/delete/', views.PostDeleteView.as_view(), name='post_delete'),
path('category/<slug:slug>/', views.CategoryPostView.as_view(), name='category_posts'),
path('tag/<slug:slug>/', views.TagPostView.as_view(), name='tag_posts'),
path('search/', views.PostSearchView.as_view(), name='post_search'),
]

# blog/views.py
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.db.models import Q
from .models import Post, Category, Tag
from .forms import PostForm

class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
ordering = ['-created_at']

class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['related_posts'] = Post.objects.filter(
        category=self.object.category
    ).exclude(id=self.object.id)[:3]
    return context

class PostCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
success_message = "文章创建成功!"

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

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

def get_queryset(self):
    queryset = super().get_queryset()
    query = self.request.GET.get('q')
    if query:
        queryset = queryset.filter(
            Q(title__icontains=query) |
            Q(content__icontains=query)
        )
    return queryset

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['search_query'] = self.request.GET.get('q', '')
    return context

下一步学习

🎨
模板系统

学习Django模板语言和前端开发

学习模板
🗃️
表单处理

掌握Django表单的创建和验证

学习表单
🔐
用户认证

学习Django内置的用户认证系统

学习认证