类视图
类视图是 Django 提供的一种组织视图代码的方式,相比函数视图,类视图可以更好地复用代码和组织逻辑。Django 还提供了许多通用视图来处理常见的 Web 开发任务。
类视图基础
基本类视图
最简单的类视图继承自 View:
from django.views import View
from django.http import HttpResponse
class HelloView(View):
"""简单的类视图"""
def get(self, request):
return HttpResponse('Hello, Django!')
def post(self, request):
return HttpResponse('POST 请求')
# urls.py
from django.urls import path
from .views import HelloView
urlpatterns = [
path('hello/', HelloView.as_view()),
]
类视图的优势
# 函数视图 - 需要手动判断请求方法
def post_view(request, pk):
if request.method == 'GET':
return render(request, 'post_detail.html', {'post': get_object_or_404(Post, pk=pk)})
elif request.method == 'POST':
# 处理 POST
pass
elif request.method == 'PUT':
# 处理 PUT
pass
# 类视图 - 按方法分离
class PostView(View):
def get(self, request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, 'post_detail.html', {'post': post})
def post(self, request, pk):
# 处理 POST
pass
def put(self, request, pk):
# 处理 PUT
pass
通用视图
Django 提供了一系列通用视图来简化常见任务:
TemplateView
用于渲染模板:
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = 'about.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '关于我们'
return context
# URL 配置
urlpatterns = [
path('about/', AboutView.as_view()),
# 或直接在 URL 中配置
path('help/', TemplateView.as_view(template_name='help.html')),
]
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']
def get_queryset(self):
"""自定义查询集"""
queryset = super().get_queryset()
category_slug = self.request.GET.get('category')
if category_slug:
queryset = queryset.filter(category__slug=category_slug)
return queryset
def get_context_data(self, **kwargs):
"""添加额外上下文"""
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
模板中可用的变量:
<!-- post_list.html -->
<h1>文章列表</h1>
<!-- 对象列表 -->
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.content|truncatewords:50 }}</p>
</article>
{% endfor %}
<!-- 分页 -->
{% if is_paginated %}
<nav>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">上一页</a>
{% endif %}
<span>第 {{ page_obj.number }} 页,共 {{ page_obj.paginator.num_pages }} 页</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">下一页</a>
{% endif %}
</nav>
{% endif %}
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'
slug_url_kwarg = 'slug' # URL 中的参数名
slug_field = 'slug' # 模型中的字段名
def get_object(self, queryset=None):
"""自定义获取对象逻辑"""
obj = super().get_object(queryset)
# 增加阅读计数
obj.views += 1
obj.save(update_fields=['views'])
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['related_posts'] = Post.objects.filter(
category=self.object.category
).exclude(pk=self.object.pk)[:5]
return context
CreateView
用于创建对象:
from django.views.generic import CreateView
from django.urls import reverse_lazy
from .models import Post
from .forms import PostForm
class PostCreateView(CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def form_valid(self, form):
"""表单验证成功时调用"""
form.instance.author = self.request.user
return super().form_valid(form)
def get_success_url(self):
"""成功后跳转的 URL"""
return self.object.get_absolute_url()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '创建文章'
return context
UpdateView
用于更新对象:
from django.views.generic import UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from .models import Post
from .forms import PostForm
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def test_func(self):
"""权限检查"""
post = self.get_object()
return post.author == self.request.user or self.request.user.is_staff
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '编辑文章'
return context
DeleteView
用于删除对象:
from django.views.generic import DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from .models import Post
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('blog:post_list')
def test_func(self):
post = self.get_object()
return post.author == self.request.user or self.request.user.is_staff
FormView
用于处理表单:
from django.views.generic import FormView
from django.urls import reverse_lazy
from .forms import ContactForm
class ContactView(FormView):
form_class = ContactForm
template_name = 'contact.html'
success_url = reverse_lazy('contact_success')
def form_valid(self, form):
"""表单验证成功"""
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
# 发送邮件
send_contact_email(name, email, message)
return super().form_valid(form)
RedirectView
用于重定向:
from django.views.generic import RedirectView
class GoToGoogleView(RedirectView):
url = 'https://www.google.com'
permanent = False # 302 临时重定向
class PostRedirectView(RedirectView):
"""根据 ID 重定向到文章详情"""
pattern_name = 'blog:post_detail'
def get_redirect_url(self, *args, **kwargs):
post = get_object_or_404(Post, pk=kwargs['pk'])
return super().get_redirect_url(slug=post.slug)
Mixin 类
Mixin 是可复用的组件,用于为类视图添加功能:
登录验证 Mixin
from django.contrib.auth.mixins import LoginRequiredMixin
class MyProtectedView(LoginRequiredMixin, View):
login_url = '/login/'
redirect_field_name = 'next'
def get(self, request):
return HttpResponse('需要登录才能访问')
权限验证 Mixin
from django.contrib.auth.mixins import PermissionRequiredMixin
class AdminView(PermissionRequiredMixin, View):
permission_required = 'blog.add_post'
raise_exception = True # 无权限时抛出 403
def get(self, request):
return HttpResponse('需要特定权限才能访问')
# 多个权限
class EditorView(PermissionRequiredMixin, View):
permission_required = ['blog.add_post', 'blog.change_post']
用户通过测试 Mixin
from django.contrib.auth.mixins import UserPassesTestMixin
class AuthorOnlyView(UserPassesTestMixin, DetailView):
model = Post
def test_func(self):
"""只有作者可以访问"""
post = self.get_object()
return self.request.user == post.author
自定义 Mixin
class CategoryContextMixin:
"""添加分类上下文的 Mixin"""
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
class SearchMixin:
"""搜索功能的 Mixin"""
def get_queryset(self):
queryset = super().get_queryset()
search = self.request.GET.get('q')
if search:
queryset = queryset.filter(title__icontains=search)
return queryset
class PostListView(CategoryContextMixin, SearchMixin, ListView):
model = Post
template_name = 'blog/post_list.html'
通用视图属性和方法
常用属性
class MyListView(ListView):
model = Post # 模型
queryset = Post.objects.filter(status='published') # 自定义查询集
template_name = 'blog/post_list.html' # 模板名称
template_name_suffix = '_list' # 模板后缀
context_object_name = 'posts' # 上下文变量名
paginate_by = 10 # 分页数量
ordering = ['-created'] # 排序
page_kwarg = 'page' # 分页参数名
allow_empty = True # 允许空列表
常用方法
class MyDetailView(DetailView):
model = Post
def get_queryset(self):
"""返回查询集"""
return Post.objects.filter(status='published')
def get_object(self, queryset=None):
"""获取单个对象"""
obj = super().get_object(queryset)
return obj
def get_context_data(self, **kwargs):
"""获取上下文数据"""
context = super().get_context_data(**kwargs)
context['extra_data'] = '额外数据'
return context
def get_template_names(self):
"""返回模板名称列表"""
if self.request.user.is_staff:
return ['blog/post_detail_admin.html']
return ['blog/post_detail.html']
def get(self, request, *args, **kwargs):
"""处理 GET 请求"""
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
"""处理 POST 请求"""
return HttpResponse('POST')
def dispatch(self, request, *args, **kwargs):
"""请求分发"""
return super().dispatch(request, *args, **kwargs)
完整示例:博客 CRUD
# views.py
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from django.contrib import messages
from .models import Post
from .forms import PostForm
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
queryset = Post.objects.filter(status='published')
category = self.request.GET.get('category')
if category:
queryset = queryset.filter(category__slug=category)
search = self.request.GET.get('q')
if search:
queryset = queryset.filter(title__icontains=search)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
context['search'] = self.request.GET.get('q', '')
return context
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_queryset(self):
return Post.objects.all()
def get_object(self, queryset=None):
obj = super().get_object(queryset)
obj.views += 1
obj.save(update_fields=['views'])
return obj
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def form_valid(self, form):
form.instance.author = self.request.user
messages.success(self.request, '文章创建成功!')
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '创建文章'
return context
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Post
form_class = PostForm
template_name = 'blog/post_form.html'
def test_func(self):
post = self.get_object()
return post.author == self.request.user or self.request.user.is_staff
def form_valid(self, form):
messages.success(self.request, '文章更新成功!')
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = '编辑文章'
return context
class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Post
template_name = 'blog/post_confirm_delete.html'
success_url = reverse_lazy('blog:post_list')
def test_func(self):
post = self.get_object()
return post.author == self.request.user or self.request.user.is_staff
def delete(self, request, *args, **kwargs):
messages.success(request, '文章删除成功!')
return super().delete(request, *args, **kwargs)
URL 配置:
# urls.py
from django.urls import path
from .views import (
PostListView, PostDetailView,
PostCreateView, PostUpdateView, PostDeleteView
)
app_name = 'blog'
urlpatterns = [
path('', PostListView.as_view(), name='post_list'),
path('post/<int:pk>/', PostDetailView.as_view(), name='post_detail'),
path('post/create/', PostCreateView.as_view(), name='post_create'),
path('post/<int:pk>/edit/', PostUpdateView.as_view(), name='post_update'),
path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post_delete'),
]
异步类视图
Django 3.1+ 支持异步类视图:
from django.views import View
from django.http import HttpResponse
import asyncio
class AsyncView(View):
async def get(self, request):
await asyncio.sleep(1)
return HttpResponse('Hello, async Django!')
class AsyncDetailView(DetailView):
model = Post
async def get(self, request, *args, **kwargs):
self.object = await self.get_object()
context = await self.get_context_data()
return self.render_to_response(context)