第9章:视图集和路由器¶
视图集[99]和路由器[100]是 Django REST Framework 中可以帮助加速 API 开发的工具。它们是视图和 URLs 之上的额外抽象层。主要好处是单个视图集可以替换多个相关视图。路由器可以为开发人员自动生成 URLs。在具有许多端点的较大项目中,这意味着开发人员必须编写更少的代码。可以说,对于有经验的开发人员来说,理解和推理少量视图集和路由器组合比一长串单独的视图和 URLs 更容易。
在本章中,我们将向现有项目添加两个新的 API 端点,并看看从视图和 URLs 切换到视图集和路由器如何用更少的代码实现相同的功能。
用户端点¶
目前我们的项目中有以下 API 端点。它们都以 api/v1/ 为前缀(为简洁起见未显示):
端点表格 | 端点 | HTTP 动词 | |------|----------| | / | GET | | /:pk/ | GET | | /rest-auth/registration | POST | | /rest-auth/login | POST | | /rest-auth/logout | GET | | /rest-auth/password/reset | POST | | /rest-auth/password/reset/confirm | POST |
前两个端点是我们创建的,而 dj-rest-auth 提供了其他五个。现在让我们添加两个额外的端点来列出所有用户和单个用户。这是许多 API 中的常见功能,它将更清楚地说明为什么将我们的视图和 URLs 重构为视图集和路由器是有意义的。
连接新端点的过程总是涉及以下三个步骤: - 模型的新序列化器类 - 每个端点的新视图 - 每个端点的新 URL 路由
从我们的序列化器开始。我们需要导入自定义用户模型,然后创建一个使用它的 UserSerializer 类。然后将其添加到我们现有的 posts/serializers.py 文件中。
代码
# posts/serializers.py
from django.contrib.auth import get_user_model # new
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ("id", "author", "title", "body", "created_at",)
class UserSerializer(serializers.ModelSerializer): # new
class Meta:
model = get_user_model()
fields = ("id", "username",)
值得注意的是,虽然我们在这里使用 get_user_model 来引用自定义用户模型,但实际上有三种不同的方法可以引用 Django 中的当前用户模型。通过使用 get_user_model,我们确保引用正确的用户模型,无论是默认用户还是自定义用户模型(如我们案例中的 CustomUser)。
接下来,我们需要为每个端点定义视图。首先将 UserSerializer 添加到导入列表中。然后创建 UserList 类(列出所有用户)和 UserDetail 类(提供单个用户的详细视图)。就像我们的文章视图一样,我们可以在这里使用 ListCreateAPIView 和 RetrieveUpdateDestroyAPIView。我们还需要通过 get_user_model 引用用户模型,因此它在顶行导入。
代码
# posts/views.py
from django.contrib.auth import get_user_model # new
from rest_framework import generics
from .models import Post
from .permissions import IsAuthorOrReadOnly
from .serializers import PostSerializer, UserSerializer # new
class PostList(generics.ListCreateAPIView):
permission_classes = (IsAuthorOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthorOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostSerializer
class UserList(generics.ListCreateAPIView): # new
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveUpdateDestroyAPIView): # new
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
如果您注意到,这里有很多重复。文章视图和用户视图都具有完全相同的查询集和序列化器类。也许可以以某种方式组合这些以节省代码?
最后我们有 URL 路由。确保导入新的 UserList 和 UserDetail 视图。然后我们可以为每个使用前缀 users/。
代码
# posts/urls.py
from django.urls import path
from .views import PostList, PostDetail, UserList, UserDetail # new
urlpatterns = [
path("users/", UserList.as_view()), # new
path("users/<int:pk>/", UserDetail.as_view()), # new
path("", PostList.as_view()),
path("<int:pk>/", PostDetail.as_view()),
]
我们完成了。确保本地服务器正在运行,然后跳转到可浏览 API 以确认一切按预期工作。
我们的用户列表端点位于:[[http://127.0.0.1:8000/api/v1/users/](http://127.0.0.1:8000/api/v1/users/](http://127.0.0.1:8000/api/v1/users/](http://127.0.0.1:8000/api/v1/users/`))
状态码是 200 OK,这意味着一切正常。我们可以看到我们的三个现有用户。每个用户的主键都有用户详细端点。所以我们的超级用户帐户位于:[[http://127.0.0.1:8000/api/v1/users/1/](http://127.0.0.1:8000/api/v1/users/1/](http://127.0.0.1:8000/api/v1/users/1/](http://127.0.0.1:8000/api/v1/users/1/`))
视图集¶
视图集是一种将多个相关视图的逻辑组合到单个类中的方法。换句话说,一个视图集可以替换多个视图。目前我们有四个视图:两个用于博客文章,两个用于用户。我们可以改为用两个视图集来模拟相同的功能:一个用于博客文章,一个用于用户。权衡是对于不熟悉视图集的 fellow 开发人员来说,可读性会有所损失。
下面是当我们换入视图集时,更新的 posts/views.py 文件中的代码样子。
代码
# posts/views.py
from django.contrib.auth import get_user_model
from rest_framework import viewsets # new
from .models import Post
from .permissions import IsAuthorOrReadOnly
from .serializers import PostSerializer, UserSerializer
class PostViewSet(viewsets.ModelViewSet): # new
permission_classes = (IsAuthorOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostSerializer
class UserViewSet(viewsets.ModelViewSet): # new
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
在顶部,我们现在从 rest_framework 导入视图集,而不是导入 generics。然后我们使用 ModelViewSet,它为我们提供列表视图和详细视图。我们不再需要像以前那样为每个视图重复相同的查询集和序列化器类!
此时,本地 Web 服务器将停止,因为 Django 抱怨缺少相应的 URL 路径。让我们接下来设置这些。
路由器¶
路由器与视图集直接配合,为我们自动生成 URL 模式。我们当前的 posts/urls.py 文件有四个 URL 模式:两个用于博客文章,两个用于用户。我们可以改为为每个视图集采用单个路由。所以是两个路由而不是四个 URL 模式。听起来更好,对吧?
Django REST Framework 有两个默认路由器:SimpleRouter 和 DefaultRouter。我们将使用 SimpleRouter,但也可以创建自定义路由器以获得更高级的功能。
更新后的代码如下所示:
代码
# posts/urls.py
from django.urls import path
from rest_framework.routers import SimpleRouter
from .views import UserViewSet, PostViewSet
router = SimpleRouter()
router.register("users", UserViewSet, basename="users")
router.register("", PostViewSet, basename="posts")
urlpatterns = router.urls
在顶行导入了 SimpleRouter 和我们的视图。路由器设置为 SimpleRouter,我们为每个视图集"注册"用户和文章。最后,我们将 URLs 设置为使用新路由器。现在启动本地服务器(python manage.py runserver)来检查我们的四个端点。首先是 [[http://127.0.0.1:8000/api/v1/users/](http://127.0.0.1:8000/api/v1/users/](http://127.0.0.1:8000/api/v1/users/](http://127.0.0.1:8000/api/v1/users/`)) 的用户列表,它是相同的。
详细视图 [[http://127.0.0.1:8000/api/v1/users/1/](http://127.0.0.1:8000/api/v1/users/1/](http://127.0.0.1:8000/api/v1/users/1/](http://127.0.0.1:8000/api/v1/users/1/)) 略有不同。它现在被称为"User Instance"而不是"User Detail",并且有一个额外的"删除"选项,它内置在ModelViewSet` 中。
权限¶
如果我们停下来考虑我们目前的 API,实际上存在一个巨大的安全问题。任何认证用户都可以在用户列表页面上添加新用户,或者在用户实例页面上编辑/删除/更新单个用户,因为 UserViewSet 没有明确的权限。这是一个大问题!
为任何 API 端点考虑权限很重要,特别是当涉及用户信息时。在这种情况下,我们希望限制仅限超级用户访问。如果我们查看官方文档中的权限页面,有一个现有的权限设置称为 IsAdminUser,这正是我们想要的。将其添加到 UserViewSet 实际上非常简单。
在 posts/views.py 文件中,在顶部导入 IsAdminUser,然后在 UserViewSet 类下,将权限类设置为 [IsAdminUser]。
代码
# posts/views.py
from django.contrib.auth import get_user_model
from rest_framework import viewsets
from rest_framework.permissions import IsAdminUser # new
from .models import Post
from .permissions import IsAuthorOrReadOnly
from .serializers import PostSerializer, UserSerializer
class PostViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthorOrReadOnly,)
queryset = Post.objects.all()
serializer_class = PostSerializer
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [IsAdminUser] # new
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
本地 Web 服务器应该自动重新启动更改的代码,所以我们可以重新访问 [[http://127.0.0.1:8000/api/v1/users/](http://127.0.0.1:8000/api/v1/users/](http://127.0.0.1:8000/api/v1/users/](http://127.0.0.1:8000/api/v1/users/`)) 的用户列表端点。
即使我们仍然以 testuser 身份登录,此端点也不可用,每个用户实例端点也是如此。
设置权限时,始终有良好的限制性项目级设置,并根据需要开放每个端点的访问权限。检查现有端点是否没有敞开也很重要,就像我们之前的用户一样,这很容易做到!
为了完成,我们应该将我们的新工作提交到 Git。
Shell
(.venv) > git status
(.venv) > git add -A
(.venv) > git commit -m "add viewsets and routers"
结论¶
视图集和路由器是强大的抽象,可以减少我们作为开发人员必须编写的代码量。然而,这种简洁性是以初始学习曲线为代价的。当您第一次使用视图集和路由器而不是视图和 URL 模式时,会感到奇怪。
最终,何时向您的项目添加视图集和路由器的决定是主观的。一个好的经验法则是从视图和 URLs 开始。随着您的 API 复杂性增加,如果您发现自己一遍又一遍地重复相同的端点模式,那么请查看视图集和路由器。直到那时,保持简单。