目前我们的 API 对于谁能编辑或删除代码片段没有任何限制。我们希望实现更高级的行为以确保:
- 代码片段始终与创建者相关联。
- 只有经过身份验证的用户才能创建片段。
- 只有片段的创建者才能更新或删除它。
- 未经过身份验证的请求应具有完全的只读访问权限。
向模型中添加信息
我们将在 Snippet
模型类中做一些更改。
首先,让我们添加几个字段。其中的一个字段将用于表示创建代码片段的用户。另一个字段将用于存储代码的高亮 HTML 表示形式。
在 models.py
中的 Snippet
模型添加以下两个字段:
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
我们还需要确保在保存模型时,使用 pygments
代码高亮库来填充高亮字段。
我们需要一些额外的导入:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
现在,我们可以向模型类添加一个 .save()
方法:
def save(self, *args, **kwargs):
"""
使用 `pygments` 库创建代码片段的高亮 HTML 表示形式。
"""
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super().save(*args, **kwargs)
完成后,我们需要更新数据库表。
通常我们会创建一个数据库迁移来实现这一点,但在这个教程中,让我们直接删除数据库并重新开始。
rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
你可能还想要创建几个不同的用户用于测试 API。最快的方法是使用 createsuperuser
命令。
python manage.py createsuperuser
为用户模型添加端点
现在我们已经有了一些用户可以使用,我们最好将这些用户的表示形式添加到我们的 API 中。创建一个新序列化器很简单。在 serializers.py
中添加:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ['id', 'username', 'snippets']
由于 User
模型上的 'snippets'
是一个反向关系,因此在使用 ModelSerializer
类时,默认情况下不会包含它,所以我们需要为它添加一个明确的字段。
我们还将向 views.py
添加几个视图。我们希望仅为用户的表示形式使用只读视图,因此我们将使用 ListAPIView
和 RetrieveAPIView
这两个通用类视图。
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
确保还导入了 UserSerializer
类
from snippets.serializers import UserSerializer
最后,我们需要通过引用它们来将这些视图添加到 API 中,在 snippets/urls.py
的 URLconf 中添加以下内容。
path('users/', views.UserList.as_view()),
path('users//', views.UserDetail.as_view()),
将片段与用户关联
现在,如果我们创建了一个代码片段,将无法将创建该片段的用户与片段实例关联起来。用户并不是作为序列化表示的一部分发送的,而是传入请求的一个属性。
我们通过覆盖片段视图上的 .perform_create()
方法来处理这个问题,该方法允许我们修改实例保存的方式,并处理传入请求或请求 URL 中隐含的任何信息。
在 SnippetList
视图类上,添加以下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
序列化器的 create()
方法现在将与请求中的验证数据一起被传递一个额外的 'owner'
字段。
更新序列化器
现在片段已经与创建它们的用户关联起来,让我们更新 SnippetSerializer
以反映这一点。在 serializers.py
中的序列化器定义中添加以下字段:
owner = serializers.ReadOnlyField(source='owner.username')
注意 :确保还将 'owner',
添加到内部 Meta
类的字段列表中。
这个字段正在做一些非常有趣的事情。source
参数控制用于填充字段的属性,并且可以指向序列化实例上的任何属性。它还可以采用上面所示的点表示法,在这种情况下,它将以与 Django 模板语言类似的方式遍历给定的属性。
我们添加的字段是未类型的 ReadOnlyField
类,与 CharField
、BooleanField
等其他类型字段相反……未类型的 ReadOnlyField
总是只读的,并将用于序列化表示,但在反序列化时不会用于更新模型实例。我们本可以在这里使用 CharField(read_only=True)
。
为视图添加所需的权限
现在代码片段已经与用户关联起来,我们希望确保只有经过身份验证的用户才能创建、更新和删除代码片段。
REST framework 包括几个权限类,可用于限制对给定视图的访问。在这种情况下,我们正在寻找的是 IsAuthenticatedOrReadOnly
,它将确保经过身份验证的请求获得读写访问权限,而未经身份验证的请求获得只读访问权限。
首先在视图模块中添加以下导入
from rest_framework import permissions
然后,在 SnippetList 和 SnippetDetail 视图类上添加以下属性。
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
为可浏览 API 添加登录功能
如果你现在打开浏览器并导航到可浏览 API,你会发现你再也无法创建新的代码片段了。为了能够这样做,我们需要能够以用户身份登录。
我们可以通过编辑项目级 urls.py
文件中的 URLconf 来添加一个用于可浏览 API 的登录视图。
在文件顶部添加以下导入:
from django.urls import path, include
在文件末尾,添加一个模式以包含可浏览 API 的登录和注销视图。
urlpatterns += [
path('api-auth/', include('rest_framework.urls')),
]
'api-auth/'
模式的实际内容可以是你想要的任何 URL。
现在如果你再次打开浏览器并刷新页面,你会看到页面右上角出现一个“登录”链接。如果你以之前创建的其中一个用户身份登录,你将能够再次创建代码片段。
一旦你创建了几个代码片段,导航到“/users/”端点,注意到每个用户的“snippets”字段中都包含与该用户关联的片段 id 列表。
对象级权限
实际上,我们希望所有代码片段都对任何人可见,但还要确保只有创建代码片段的用户才能更新或删除它。
为此,我们需要创建一个自定义权限。
在 snippets 应用中,创建一个新文件,permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
自定义权限,仅允许对象的所有者编辑它。
"""
def has_object_permission(self, request, view, obj):
# 允许任何请求的读取权限,
# 因此我们始终允许 GET、HEAD 或 OPTIONS 请求。
if request.method in permissions.SAFE_METHODS:
return True
# 写入权限仅允许片段的所有者拥有。
return obj.owner == request.user
现在,我们可以通过编辑 SnippetDetail
视图类上的 permission_classes
属性,将此自定义权限添加到我们的片段实例端点中:
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
确保还导入了 IsOwnerOrReadOnly
类。
from snippets.permissions import IsOwnerOrReadOnly
现在,如果你再次打开浏览器,你会发现如果以与创建代码片段的用户相同的身份登录,“删除”和“PUT”操作才会出现在片段实例端点上。
与 API 进行身份验证
由于我们现在在 API 上设置了一系列权限,因此如果我们想要编辑任何片段,就需要对我们的请求进行身份验证。我们尚未设置任何身份验证类,因此当前应用的是默认设置,即 SessionAuthentication
和 BasicAuthentication
。
当我们通过网络浏览器与 API 交互时,我们可以登录,然后浏览器会话将为请求提供所需的身份验证。
如果我们尝试在不进行身份验证的情况下创建一个片段,我们将得到一个错误:
http POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"detail": "未提供身份验证凭据。"
}
我们可以通过包含之前创建的其中一个用户的用户名和密码来发出成功的请求。
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
{
"id": 1,
"owner": "admin",
"title": "foo",
"code": "print(789)",
"linenos": false,
"language": "python",
"style": "friendly"
}
总结
现在我们的 Web API 拥有了相当精细的权限设置,以及系统用户及其创建的代码片段的端点。
在教程的第 5 部分,我们将介绍如何通过创建一个用于高亮片段的 HTML 端点,并通过使用超链接来改进系统内关系的内聚性,从而将所有内容整合在一起。