官网地址:http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/
当前我们的API 并没有限制谁可以编辑或删除snippets代码, 我们需要一些更高级的行为来确保:
. snippets代码总是与创建者联系在一起
. 只有认证的用户才可以创建 snippets
. 只有snippet的创建者才可以更新或删除信息
. 未认证的请求只能有只读访问权限
1. model中添加信息
在我们的Snippet
model类中做一些修改. 首先,添加一些字段。其中的一个字段用来代表创建代码段的用户。另一个字段将用于存储代码的高亮HTML表示形式。
在Snippet
的model文件 models.py
中添加两个字段
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
我们还需要确保当model保存后,我们使用pygments
高亮代码库填充高亮字段。
我们需要扩展的imports:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
现在我们可以添加 .save()
方法到我们modles类中:
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
当都添加完成后,我们需要更新我们的数据库表。正常情况下我们需要通过数据库的migration来完成, 但此并非本节的目的, 这里就直接删除数据库重建了。
rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
你可能还要创建一些不同用户用来测试API。最快的方式是用createsuper
命令
python manage.py createsuperuser
2. 添加用户模块端
现在我们已经有了一些用户,我们最好在我们的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'
是User 模型的一个反向关联关系, 在使用ModelSerializer
类的时候默认不会包含,所以我们需要为它添加一个显式字段.
我们还需要在 views.py
中添加一些视图. 我们只是为用户提供只读视图.so我们将使用 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中, 通过从URL 配置中引用并添加以下信息到 urls.py
中:
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
3. 关联 Snippet 和 User
现在,如果我们创建一个snippet代码段, 没有办法通过snippet实例关联创建代码段的用户。用户不是作为序列化表示的一部分发送的,而是传入Request的属性。
我们处理的方法是在snippet视图中重写.perform_create()
方法, 允许我们修改如何管理实例保存方式,并处理传入请求或请求URL中隐含的任何信息
在SnippetList
类视图中,添加下面的方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
在我们的序列化方法create()
将通过一个额外的 owner
字段,值为请求request数据中的用户
4. 更新 Serializer
现在 snippets已经和创建的用户有了关联, 现在更新我们的 SnippetSerializer
来验证。添加如下字段到序列化文件serializers.py
owner = serializers.ReadOnlyField(source='owner.username')
备注: 请确认你已经添加'owner'
到内部的 Meta
类的字段中
这个字段会做一些很有趣的事情, source
参数控制填充字段的属性,并且可以执行序列化实例的任何属性。它也可以采用上面显示的点划线,在这种情况下,它将以与Django模板语言一起使用的相似方式遍历给定的属性。
我们添加的字段是一个类型化ReadOnlyField
类,与其他类型字段相反,比如CharField
,BooleanField
等, 类型化ReadOnlyField
总是只读的,将用于序列化表示. 不可用于更新model的实例时反序列化。我们也可以在这里使用 CharField(read_only=True)
.
5. 在视图中添加需要的权限
现在snippets代码段已经和用户做了关联, 我们想要确信之后认证的用户才可以创建、更新和删除代码段
REST框架包含很多我们可以用来限制谁可以访问给定的视图的权限类。在这之中我们要看的是 IsAuthenticatedOrReadOnly
模块,能够确保请求得到读写访问权限。
首先, 在views视图中添加如下模块:
from rest_framework import permissions
然后, 在 SnippetList
和 SnippetDetail
类视图中添加 如下属性
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
6. 添加登录浏览 API
如果你这个时候打开一个浏览器来浏览 API , 你会发现你还不能创建新的代码段, 为了能实现该操作, 我们需要作为一个用户进行登录
我们可以添加登录视图用来浏览API,在我们的项目级别的 urls.py
文件编辑 URL 配置
在顶部导入如下模块:
from django.conf.urls import include
然后,在文件的尾部,添加一个规则包含登录和登出的视图
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
以上配置中, r^api-auth/
部分可以是你想使用的任何URL。唯一要限制的是include
部分的命名空间必须使用 rest_framework
,在Django 1.9+中,REST框架将设置命名空间,因此您可以将其删除。
现在如果你打开浏览器,重新刷新浏览器你将会在页面右侧顶部看到一个 ‘Login’ 的连接。如果你用之前创建的用户登录,你就可以再次创建代码段了。
一旦你已经创建了一些代码段, 在浏览器地址栏中导航到 /users/
,将会显示的是与每个用户关联的代码段ID的列表,在每个用户的snippets
字段中
7. 对象级别的权限
事实上, 我们希望所有的代码片段对任何人都是可见的。但也要确保只有创建代码段的用户能够更新或删除它。
为了实现此功能。
我们需要创建一个自定义的权限
在 snippets
APP, 创建一个新文件permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
现在我们可以将自定义权限添加到我们的 snippet 实例, 通过编辑 permission_classes
属性到 SnippetDetail
类视图:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
请确认已经导入了 IsOwnerOrReadOnly
类:
from snippets.permissions import IsOwnerOrReadOnly
现在,如果你再次打开浏览器, 你会发现 ‘DELETE’ 和 ‘PUT’ 动作只出现在你登录的并创建了该代码段的用户的代码段后面。
通过API认证
因为我们现在已经在API中设置了权限, 如果我们需要编辑 snippets 则需要对请求进行认证。 我们还没有设置任何认证类,因此采用默认应用 SessionAuthentication
和 BasicAuthentication
当我们通过浏览器与API进行交互时, 我们可以登录,然后浏览器会话将为请求提供所需的身份验证。
如果我们未进行认证而创建snippet时,会返回错误:
http POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"detail": "Authentication credentials were not provided."
}
我们可以通过我们之前创建的一个用户的用户名和密码来成功后期请求。
http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
{
"id": 1,
"owner": "tom",
"title": "foo",
"code": "print 789",
"linenos": false,
"language": "python",
"style": "friendly"
}
总结
我们现在已经在我们的Web API上获得了相当精细的一组权限,并为系统的用户和他们创建的代码段提供了endpoint。