目前我们的API对于谁可以编辑或者删除代码段没有任何的限制。我们希望能有一些更加高级的行为来确保:
让我们给之前的Snippet
模型添加一些信息。首先,先添加一些字段。因为要关联到谁创建了代码段,所以要添加creator字段。其他字段用来存储高亮的HTML。
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted=models.TextField()
我们也需要确保当模型被保存时,使用pygments
代码高亮库来填充highlighted
字段。
我们需要一些额外的引入:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
此时,我们可以为我们的模型添加.save()
方法:
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)
然后,重新创建数据库:
rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
你可能想要多创建几个不同的用户用以测试,最方便的方法莫过于使用createsuperuser
命令了。
python manage.py createsuperuser
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')
我们使用了ModelSerializer,它会包含User这个Model的所有字段。但是由于’snippets’对于User来说是一个反向的关系,所以UserSerializer默认是不会包含snippets
的,我们需要自行添加。
我们也需要添加些View,但对于User来说,只读就可以了,因此我们使用ListAPIView
和RetrieveAPIView
来生成基于类的视图。
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
最后添加上新的urls映射:
url(r'^users/$', views.UserList.as_view()),
url(r'^user/(?P[0-9]+)/$' , views.UserDetail.as_view())
到目前为止,如果我们创建一个代码段,还没有办法把User的信息添加到Snippet对象中去。
我们的解决方案是重写snippet视图的.perform_create()
方法,允许我们修改实例的保存方式,并且处理所有隐含在请求或者请求URL中的信息。
在SnippetList中,添加如下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
现在snippet的create()
方法在调用时将会传入一个owner
字段,和validated_data一起传入。
现在,代码片段已经和创建他们的用户相关联。让我们来更新SnippetSerializer
。在serializers.py
中添加如下代码:
owner = serializers.ReadOnlyField(source='owner.username')
注意:请确保你在Meta中也添加了owner,
。
这个字段做了一些很有意思的事情。source
字段控制哪个属性用于填充字段,并且可以指向序列化实例的任何一个属性。而且还可以像上面一样,用点号获取,这样它会遍历给定的属性,就类似于Django的模板语言。
这里我们添加的Field是一个无类型Field——ReadOnlyField
。不同于有类型的Field,该类型的Filed只读,只用于做展示,不能用于更新。这里我们也可以使用CharField(read_only=True)
。
现在我们已经将code snippets和它们的创建者关联起来了,我们想要确保只有认证了的用户才能创建、更新和删除代码段。
REST框架提供了一套权限类,可以让我们来限制可以访问View的用户。我们当前的案例中,我们是希望没认证的用户只能读取,认证的可以修改,所以应该使用IsAuthenticatedOrReadOnly
。
如何使用呢?
首先要引入permissions模块
from rest_framework import permissions
然后再SnippetList和SnippetDetail视图都加入以下语句:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
此时,如果你打开浏览器,访问Snippet的API,你会发现你现在已经创建不了新的代码段了。为了能够创建代码,我们必须登录。
我们可以添加一个登录视图,通过编辑URLconfig来添加。编辑项目最顶层的urls.py
文件。
from django.conf.urls import include
在文件的结尾处添加一个pattern,用于登录和注销。
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
现在如果你现在再次打开浏览器,可以看到登录按钮,登陆后就可以创建或修改代码段了。
事实上,我们只是控制了认证的人拥有写权限,但是我们预期的是只有当前项的创建者才能修改或删除该值。
为了能够实现这种控制,我们需要创建一个自定义的权限。
在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视图中了,通过编辑SnippetDetail视图的permission_class
属性:
from snippets.permissions import IsOwnerOrReadOnly
....
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
现在,如果你重新打开浏览器,你会发现只有你创建的Snippet才会出现DELETE
或者PUT
的动作。
目前,我们没有设置任何的Authentication类,所以默认情况下是使用SessionAuthentication
和BasicAuthentication
。
当我们通过浏览器访问API,我们可以通过登录页面登录,并且浏览器的session会保存认证所必须的数据。
如果我们使用命令行交互,则必须为每一个请求都显式指定验证数据。
如果我们项要创建一个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": 5,
"owner": "tom",
"title": "foo",
"code": "print 789",
"linenos": false,
"language": "python",
"style": "friendly"
}
现在我们已经为我们的Web API提供了相当细粒度的权限控制了。
在下一章中,我们讲学习如何将一切联系在一起,创造出高亮代码段的HTML endpoint,并利用超链接关系提高我们API之间的关联。