此文档为官方文档的翻译,原文地址:https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/

目前,我们的API对谁可以编辑或删除代码片段没有任何限制。我们想要一些更高级的行为来确保:

  • Code snippets (代码段)总是与创建者相关联。
  • 只有经过身份验证的用户才能创建代码段。
  • 只有代码片段的创建者可以更新或删除它。
  • 未经身份验证的请求应具有完全的只读访问权限。

为模型添加信息

我们将对 Snippet 模型类进行一些更改。首先,让我们添加几个字段。其中一个字段将用于表示创建代码段的用户。另一个字段将用于存储代码的突出显示的 HTML 表示形式。

  1. owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
  2. highlighted = models.TextField() # 存储高亮的HTML显示的代码

我们还需要确保在保存模型时,使用 pygments库填充highlighted的字段。

导入以下工具:

  1. from pygments.lexers import get_lexer_by_name
  2. from pygments.formatters.html import HtmlFormatter
  3. from pygments import highlight

现在重写模型的save()方法

  1. def save(self, *args, **kwargs):
  2. """
  3. 使用“pygments”库创建一个高亮显示的 HTML代码段的表示形式。
  4. """
  5. lexer = get_lexer_by_name(self.language)
  6. linenos = 'table' if self.linenos else False
  7. options = {'title': self.title} if self.title else {}
  8. formatter = HtmlFormatter(style=self.style, linenos=linenos,
  9. full=True, **options)
  10. self.highlighted = highlight(self.code, lexer, formatter)
  11. super().save(*args, **kwargs)

完成此操作后,我们需要更新数据库表。通常,我们会创建一个数据库迁移以做到这一点,但是出于本教程的目的,让我们删除数据库并重新开始。

  1. rm -f db.sqlite3
  2. rm -r snippets/migrations
  3. python manage.py makemigrations snippets
  4. python manage.py migrate

为了测试API的展示效果,我们需要创建一个超级用户:

  1. python manage.py createsuperuser

为模型添加API端点

什么是 API 端点?

简单地说,端点是通信通道的一端。当 API 与另一个系统交互时,此通信的接触点被视为端点。对于 API,端点可以包含服务器或服务的 URL。每个端点都是 API 可以访问执行其功能所需的资源的位置。 API 使用“请求”和“响应”工作。当 API 从 Web 应用程序或 Web 服务器请求信息时,它会收到响应。API 发送请求的位置和资源所在的位置称为端点

(所以说,该标题翻译成人话就是:为模型添加可访问的url(这个过程包含了序列化、编写试图、指定url映射规则)

既然我们有了一些用户可以使用,我们最好将这些用户的表示添加到我们的API中。

serializers.py 添加:

  1. from django.contrib.auth.models import User
  2. class UserSerializer(serializers.ModelSerializer):
  3. snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
  4. class Meta:
  5. model = User
  6. fields = ['id', 'username', 'snippets']

因为snippets是 User 模型上的反向关系,在使用 ModelSerializer 类时,默认情况下不会包含它,所以我们需要为它添加一个显式字段。

接下来是添加视图,因为要做只读视图,所以我们用 ListAPIViewRetrieveAPIView 这两个通用类视图。

  1. from django.contrib.auth.models import User
  2. from snippets.serializers import UserSerializer
  3. class UserList(generics.ListAPIView):
  4. queryset = User.objects.all()
  5. serializer_class = UserSerializer
  6. class UserDetail(generics.RetrieveAPIView):
  7. queryset = User.objects.all()
  8. serializer_class = UserSerializer

最后,我们需要通过从 URL conf 中引用这些视图,将它们添加到 API 中。将以下内容添加到 snippets/urls.py 中:

  1. path('users/', views.UserList.as_view()),
  2. path('users/<int:pk>/', views.UserDetail.as_view()),

将代码段与用户关联

现在,如果我们创建了一个代码片段,却没有办法将创建该代码片段的用户与该代码片段实例关联起来。用户不是作为序列化表示的一部分发送的,而是传入请求的一个属性。

我们处理的方式是通过在Snippet视图上覆盖.perform_create()方法,它允许我们修改实例保存的管理方式,并处理在传入请求或请求URL中隐含的任何信息。

重写 SnippetList 类的.perform_create()方法:

  1. def perform_create(self, serializer):
  2. serializer.save(owner=self.request.user)

我们的序列化程序的 create()方法现在将被传递一个附加的owne”字段,以及来自请求的验证数据。

更新serializer

现在代码段与创建它们的用户关联起来了,让我们更新 SnippetSerializer来反映这一点。向 serializers.py 中的序列化程序定义添加以下字段:

  1. class SnippetSerializer(serializers.ModelSerializer):
  2. owner = serializers.ReadOnlyField(source='owner.username') # 如果不加这个,默认用owner.id显示,太不友好!
  3. class Meta:
  4. model = Snippet
  5. fields = ['id', 'title', 'code', 'linenos', 'language', 'style', 'owner'] # 这里也要加上哦!

这个字段很有意思,source参数控制用于填充字段的属性,并且可以指向序列化实例上的任何属性。它还可以使用上面显示的点符号,在这种情况下,它将以与 Django 的模板语言类似的方式遍历给定的属性。

我们添加的字段是非类型化的 ReadOnlyField 类,与其他类型化字段(如 CharFieldBooleanField 等)形成对比。.非类型化的 ReadOnlyField 始终是只读的,并且将用于序列化表示,但是当模型实例被反序列化时,会用于更新它们。我们也可以在这里使用 CharField(read_only=True)

向视图添加所需权限

既然代码段与用户关联,我们希望确保只有经过身份验证的用户才能创建、更新和删除代码段。

REST 框架包含许多权限类,我们可以使用这些类来限制谁可以访问给定的视图。在这种情况下,我们寻找的是 IsAuthenticatedOrReadOnly,它将确保经过身份验证的请求获得读写访问,未经身份验证的请求获得只读访问。

  1. from rest_framework import permissions

SnippetListSnippetDetail 这两个类添加以下属性:

  1. permission_classes = [permissions.IsAuthenticatedOrReadOnly]

为Browsable API添加登录

如果打开浏览器并导航到可浏览的API,您将发现无法再创建新的代码段。为了做到这一点,我们需要能够以用户身份登录。

我们可以通过在项目级别的 urls.py 文件中编辑 URLconf 来添加一个登录视图,以便与可浏览的 API 一起使用。

加入下面代码

  1. urlpatterns += [
  2. path('api-auth/', include('rest_framework.urls')),
  3. ]

这里的api-auth自己可以随意更改。

现在,如果你再次打开浏览器并刷新页面,你会在页面的右上角看到一个“ Login”链接。如果您以前面创建的用户之一的身份登录,就可以再次创建代码片段。

创建了一些代码片段之后,导航到/users/端点,并注意在每个用户的“ snippets”字段中,该表示包含与每个用户关联的片段 id 列表。

对象级权限

实际上,我们希望所有代码片段对任何人都可见,但也要确保只有创建代码片段的用户才能更新或删除它。

为此,我们需要创建一个自定义权限。

在 snippets 应用程序中,创建一个新文件permissions.py

  1. from rest_framework import permissions
  2. class IsOwnerOrReadOnly(permissions.BasePermission):
  3. """
  4. 自定义权限,仅允许对象所有者对其进行编辑。
  5. """
  6. def has_object_permission(self, request, view, obj):
  7. # 任何请求都允许读权限,所以我们总是允许 GET、 HEAD 或 OPTIONS 请求。
  8. if request.method in permissions.SAFE_METHODS:
  9. return True
  10. # 只允许代码片段的所有者拥有写权限。
  11. return obj.owner == request.user

在我们可以通过编辑 SnippetDetail视图类上的permission_ classes属性,将这个自定义权限添加到 snippet实例端点:

  1. from snippets.permissions import IsOwnerOrReadOnly # 导入
  2. class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
  3. queryset = Snippet.objects.all()
  4. serializer_class = SnippetSerializer
  5. permission_classes = [permissions.IsAuthenticatedOrReadOnly,
  6. IsOwnerOrReadOnly] # 添加这个

现在,如果你再次打开浏览器,你会发现只有当你作为创建代码片段的同一个用户登录时,DELETEPUT操作才会出现在代码片段实例端点上。

使用 API 进行身份验证

因为我们现在对 API 有一组权限,所以如果我们想编辑任何代码片段,就需要对我们的请求进行身份验证。我们还没有设置任何身份验证类,因此当前应用的是缺省值,即 SessionAuthenticationBasicAuthentication

当我们通过 web 浏览器与 API 交互时,我们可以登录,浏览器会话将为请求提供所需的认证。

如果我们通过编程方式与 API 交互,我们需要显式地为每个请求提供身份验证凭据。

如果我们尝试创建片段而无需进行身份验证,我们将获得错误:

  1. http POST http://127.0.0.1:8000/snippets/ code="print(123)"
  2. {
  3. "detail": "Authentication credentials were not provided."
  4. }

通过包含前面创建的用户之一的用户名和密码,我们可以成功地进行请求:

  1. # 下面的-a是authenticate的缩写
  2. http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
  3. {
  4. "id": 1,
  5. "owner": "admin",
  6. "title": "foo",
  7. "code": "print(789)",
  8. "linenos": false,
  9. "language": "python",
  10. "style": "friendly"
  11. }

总结

现在,我们已经获得了一组相当细粒度的对 Web API 的权限,以及系统用户和他们所创建的代码片段的API端点。

在本教程的第5部分,我们将看看如何通过为我们突出显示的代码片段创建一个 HTML 端点来将所有内容联系在一起,并通过使用系统内部关系的超链接来提高 API 的内聚性。