当前,我们的API并没有权限设定,限定哪些用户可以编辑或删除。因此我们需要一些更加高级的行为来确保:
每一个代码片段(Code Snippet)实例都有一个创建者(creater);
只有相应授权的用户才可以创建代码片段实例;
每个代码片段只能被它的创建者更新或删除;
非授权的请求,只有只读的权限。
模型中添加用户类
现在,我们需要修改一下我们的Snippet模型类。首先,我们需要添加一些域:
一个域是用户域,用来表示代码片段是被哪个用户创建的;
一个域是用来存储代码片段的高亮HTML代码。
在model.py文件中的Snippet类中添加如下域:
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE) highlighted = models.TextField()
同时,我们还需要确保我们在模型实例保存到数据库时,highlighted域也通过pygments代码高亮库存储了相应数据。
在此,我们需要引入一些库:
from pygments.lexers import get_lexer_by_name from pygments.formatters.html import HtmlFormatter from pygments import highlight
现在我们给我们的模型类添加一个.save()的方法:
def save(self, request, *args, **kwargs): """ 使用pygments库创建一个高亮的HTML代码内容。 :param request: :param args: :param kwargs: :return: """ 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(code=self.code, lexer=lexer, formatter=formatter) super(Snippet, self).save(*args, **kwargs)
完成上面代码之后,我们需要更新一下数据表。
python manage.py makemigrations python manage.py migrate
我们现在可以先新建几个用户用于后续测试我们的API。最简单的方式就是使用createsuperuser命令
python manage.py createsuperuser
用户类添加序列化器和视图函数
现在我们来为我们的用户类添加序列化器和视图函数。
创建序列化器,我们直接在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类的时候,snippets不会自动被包括进来,所以我们需要添加一个域来指明。
在views.py文件中,我们也添加几个视图。现在我们只添加一下只读的视图,使用generic class based view添加ListAPIView和RetrieveAPIView。
from django.contrib.auth.models import User from snippets.serializers import UserSerializer class UserList(generic.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer class UserDetail(generic.DetailView): queryset = User.objects.all() serializer_class = UserSerializer
关联Users和Snippets
现在,如果我们创建一个代码片段(Code snippet),是没有办法将该代码片段和创建它的用户关联起来的。用户信息并不是通过序列化的信息传递过来的,而是在传过来的request中的属性内。
解决这个问题,我们需要重载一下我们snippet视图函数的.perform_create()方法,让我们可以设定实例是如何被保存的,处理在request中的以及在请求的URL中的信息。
在SnippetList视图类中,添加如下方法:
def perform_create(self, serializer): serializer.save(owner=self.request.user)
现在我们序列化器serializer中的create()方法就会被传禁区一个owner域,以及其它request中的validated data。
更新序列化器
现在我们的代码片段实例已经和创建它的用户关联起来了,现在我们更新一下我们的SnippetSerializer.
在serializers.py文件中添加如下代码:
owner = serializers.ReadOnlyField(source='owner.username')
另,同时需要在内部的Meta类中的fields域中添加owner
上面添加的owner这个域可以做一些有趣的事情。其中的source参数可以指向序列化的实例中的任意属性,用来控制哪个属性用来生成一个域。source参数的值可以使用.号分隔符显示属性的路径。
我们上面添加的域是一个无类型的ReadOnlyField类, 和其它的有类型的域不同,比如CharField, BooleanField等。无类型的ReadOnlyField总是只读的,用于序列化的数据上。但是不会用在更新模型实例时的反序列化的过程中。
我们在此也可以使用CharField(read_only=True)。
视图中添加权限
现在我们的代码片段实例(code snippet)已经和用户(user)关联起来了,现在我们需要确保只有授权用户才能创建,更新或者删除代码片段实例。
REST framework里面有一系列的权限类(Permission class),我们可以用来限制一个特定的视图函数可以由哪些用户来使用。
在我们当前的例子中,我们可以使用IsAuthenticatedOrReadOnly类。这个类可以确保被授权的用户可以拥有读写权限,而未被授权的用户只具有只读的权限。
在我们的视图中引入permissions类
from rest_framework import permissions
然后,在SnippetList和SnippetDetail视图类中添加如下属性:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
浏览器可视API中添加登陆选项
如果现在打开浏览器,查看我们的API,你会发现,现在我们没有了创建新实例的权限了。为了创建新的实例,我们需要登陆。
我们可以通过编辑项目目录下的urls.py文件,新增一个登陆视图。
urlpatterns += [ path('api-auth/', include('rest_framework.urls')),]
path中的'api-auth/'可以是你自己任意定义的。
现在重新打开浏览器并刷新页面,你可以在页面的右上角看到一个"Login"的链接。使用之前已经创建的用户登陆进去,发现,我们现在有可以继续创建代码片段实例了。
在创建了几个代码实例之后,使用浏览器进入到'/users/'下,可以看到,每个用户下的'snippets'域中会显示该用户创建的代码片段实例。
对象级别的权限
现在,我们需要代码片段实例对象能对所有的用户可见,但是同时也需要保证只有创建该对象的才有权限对其进行修改或者删除。
为了达到这个目的,现在我们需要创建一个自己的Permission类。
在snippets app中,我们新建一个permission.py文件。
from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): def has_object_permission(self, request, view, obj): if request.method in permission.SAFE_METHOD: return True return obj.owner == request.user
然后,我们将我们创建的Permission类添加到我们的snippet实例端,在SnippetDetail视图类中的permission_classess属性中添加它。
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
在上述代码之前需要引入我们自定义的Permission类
from snippets.permissions import IsOwnerOrReadOnly
现在,重新打开浏览器,能发现在我们登陆用户创建的代码片段实例的右边会显示'DELETE'和'PUT'操作选项。
现在我们的API设定了权限,如果要对snippet实例进行编辑,我们在发送request请求时也加上认证信息。我们现在还没有设置任何的认证类(authentication class),所以当前使用的是默认的认证类,即SessionAuthentication和BasicAuthentication。
当我们使用浏览器访问API时,我们需要每一个请求都添加认证信息。
如果我们现在试着没有认证情况下去创建一个snippet,会返回错误信息。
http POST http://127.0.0.1:8000/snippets/ code="print(123)" HTTP/1.1 403 Forbidden Allow: GET, POST, HEAD, OPTIONS Content-Length: 58 Content-Type: application/json Date: Tue, 16 Jul 2019 10:09:20 GMT Server: WSGIServer/0.2 CPython/3.6.0 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "detail": "Authentication credentials were not provided." }
我们可以使用之前创建的用户名和密码信息包含到我们的请求信息中,我们就可以成功获取到对应的响应信息。
http -a zhangsan:123456 POST http://127.0.0.1:8000/snippets/ code="print(789)" HTTP/1.1 201 Created Allow: GET, POST, HEAD, OPTIONS Content-Length: 113 Content-Type: application/json Date: Tue, 16 Jul 2019 10:10:19 GMT Server: WSGIServer/0.2 CPython/3.6.0 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "code": "print(789)", "id": 4, "language": "python", "linenos": false, "owner": "zhangsan", "style": "friendly", "title": "" }
总结
现在,我们就得到了一个具有比较完善的权限管理的Web API。可以正确查看到系统的用户信息以及用户创建的代码片段信息。
在下一篇文章中,我们来看一下如何通过为我们的高亮代码片段创建一个HTML文件将所有的信息整合起来。通过代码内的超链接将我们的API联系起来。
本文中的源码可以在Github上查看。点我查看本文源码
转载请注明:禅思 » Django Rest Framework学习总结-4:认证与权限(Authentication&Permissions)?