视图是Django应用的核心,负责处理Web请求并返回响应。URL配置则将URL映射到相应的视图函数。
在Django的MTV(Model-Template-View)架构中:
用户访问URL
URLconf 匹配
执行视图函数
生成HTTP响应
简单的Python函数,接收请求返回响应
基于类的视图,提供更好的代码组织和复用
Django提供的通用视图,处理常见场景
URL配置(URLconf)是一个Python模块,它定义了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配置:
# 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模式的各个部分:
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} 的个人资料")
在函数视图中处理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路径参数和查询字符串:
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的类视图基于面向对象设计,提供了清晰的继承结构。
所有类视图的基类,定义了基本的请求处理方法。
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请求")
用于显示模板的通用视图。
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
用于显示对象列表的通用视图。
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
用于显示单个对象详情的通用视图。
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')
基本的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'
)
渲染模板并返回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)
重定向到其他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')
返回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。
# 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>
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,使用反向解析
简单逻辑用函数视图,复杂功能用类视图
使用装饰器保护敏感视图
使用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