教程4 - 验证和权限
验证和权限
目前我们的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(Snippet, self).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']
因为snippets
在用户模型上是反向关系, 在使用ModelSerializer
类时,默认情况下不会包括它, 因此我们需要为其添加一个显式字段。
我们还将在views.py中添加几个视图, 我们只想对用户表示使用只读视图, 因此我们将使用ListAPIView
和RetrieveAPIView
通用的基于类的视图。
from django.contrib.auth.models import User
from snippets.serializers import UserSerializer
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中, 通过从URL 配置文件引用它们。将以下内容添加到snippets/urls.py
中的模式。
path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),
将代码片段与用户相关联
现在,如果我们创建了一个代码片段, 无法将创建该代码段的用户与该代码段实例相关联。用户不是作为序列化表示的一部分发送的,而是作为传入请求的属性的。
我们处理此问题的方法是在代码段视图中覆写.perform_create()
方法, 允许我们修改实例保存的管理方式,以及处理传入请求或请求的URL中隐含的任何信息。
在SnippetList
视图类上,添加以下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
现在将向序列化程序的create()
方法传递一个附加的owner
字段,以及来自请求的已验证数据。
更新我们的序列化器
现在,代码段与创建它们的用户相关联,让我们更新SnippetSerializer
来反映这一点, 将以下字段添加到seralizers.py
中的序列化程序定义中:
owner = serializers.ReadOnlyField(source='owner.username')
注意: 确保您还向内部
Meta
类的fields
列表中添加了owner
。
这个字段正在做一些非常有趣的事情。source
参数控制哪个属性用于填充字段,并可以指向序列化实例上的任何属性。它也可以采用上面所示的点符号, 在这种情况下,它将遍历给定的属性,就像使用Django的模板语言一样。
我们添加的字段是未类型化的ReadOnlyField
类, 与其他类型的字段相比(例如CharField
, BooleanField
等等),未类型化的ReadOnlyField
始终是只读的, 并将用于序列化表示, 但反序列化后将不会用于更新模型实例, 我们也可以在这里使用CharField(read_only=True)
。
向视图添加所需的权限
现在,代码片段已与用户相关联, 我们希望确保只有经过身份验证的用户才能创建,更新和删除代码段。
REST框架包含许多权限类,我们可以使用这些权限类来限制谁可以访问给定视图, 在这种情况下,我们正在寻找的是IsAuthenticatedOrReadOnly
,
这将确保通过身份验证的请求具有读写访问权限, 未经身份验证的请求将获得只读访问权限。
首先在视图模块中添加以下导入:
from rest_framework import permissions
然后,将以下属性添加到SnippetList
和SnippetDetail
视图类中。
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
将登录添加到Browsable API
如果您现在打开浏览器并导航到可浏览的API, 您会发现您不再能够创建新的代码段。为此,我们需要能够以用户身份登录。
我们可以添加一个用于可浏览API的登录视图,通过在我们的项目级urls.py
文件中编辑URLconf。
在文件顶部添加以下导入:
from django.conf.urls import include
并在文件末尾添加一个模式,以包括可浏览API的登录和注销视图。
urlpatterns += [
path('api-auth/', include('rest_framework.urls')),
]
模式的api-auth/
部分实际上可以是您想要使用的任何URL。
现在,如果您再次打开浏览器并刷新页面,您将在页面右上方看到“登录”链接, 如果以先前创建的用户身份登录,则可以再次创建代码段。
一旦您创建了一些代码片段, 导航到“ / users /”端点, 可以注意到,该表示在每个用户的“snippets”字段中包含与每个用户关联的代码段id的列表。
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"username": "admin",
"snippets": [
1,
2,
3
]
}
]
}
对象级权限
真的,我们希望所有人都能看到所有代码段, 但还要确保只有创建了代码段的用户才能更新或删除它。
为此,我们将需要创建一个自定义权限。
在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
现在,如果您再次打开浏览器, 如果您以创建代码段的同一用户身份登录,则发现“ DELETE”和“ PUT”操作仅出现在代码段实例端点上。
使用API进行身份验证
因为我们现在对API有一组权限, 如果要编辑任何代码段,则需要对我们的请求进行身份验证。
我们尚未设置任何身份验证类, 因此,当前应用的是默认值, 分别是SessionAuthentication
和BasicAuthentication
。
当我们通过web浏览器与API交互时, 我们可以登录, 然后浏览器会话将为请求提供所需的身份验证。如果我们以编程方式与API进行交互,则需要在每个请求上显式提供身份验证凭据.
如果我们尝试在不进行身份验证的情况下创建代码段,则会收到错误消息
http POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"detail": "Authentication credentials were not provided."
}
我们可以通过包含我们先前创建的用户之一的用户名和密码来发出成功的请求。
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端点来将所有内容结合在一起,并通过对系统内的关系使用超链接来提高API的内聚性。