跳到主要内容

类视图

类视图是 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)