Overview
前言
本文基于:
假设当前已有 DRF 的 model – snippet,并且安装运行好了 rest_auth 模块。
这篇博客目的是为 snippet 中的数据添加 owner;即从 SQL 数据库角度来说,为 snippet TABLE 的每一行数据都加上它们从属于哪一个用户的外键。
其次,在 LIST 和 DETAIL 都限定了只有 owner 才可以访问 snippet 中属于 owner 的数据。
1. 更新 model
1.1 添加外键
<django-proj>/snippet/models.py
:
class Snippet(models.Model):
owner = models.ForeignKey('auth.User', related_name='snippets',
on_delete=models.CASCADE)
[...]
2. 更新 serializer
对 fields
添加 owner
,以及对 owner
做 .ReadOnlyField
限制。
<django-proj>/snippet/serializers.py
from rest_framework import serializers
from .models import Snippet
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Snippet
fields = ['owner',
'id', 'title', 'code',
'linenos', 'language', 'style']
3. 更新 view – LIST, DETAIL
因为 detail 部分相对容易,所以放在前面
3.1 Detail View
detail 的访问如:http://<domain>:<port>/api/snippet/3/
我们想要限制只在 pk==3
这条数据的 owner 是当前访问的的用户的时候,才允许当前用户查看、修改等,这样的实现如下:
[...]
from rest_framework import permissions
[...]
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
[...]
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsOwner, ) # add permission_classes
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
为什么:N/A
3.2 List View
3.2.1 create at List View endpoint
在考虑”读取“当前用户的所有 model(Snippet) 数据之前,.../api/snippet/
这个 endpoint 还承担着 Create 的功能(generics.ListCreateAPIView
),本文要实现 create 一条数据的时候,owner (ForeignKey
) 应当绑定到当前对应的用户上。
因此在 List View 中,我们先考虑和实现这一点:
class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
可以看到,我们只是为 SnippetList
重写了 perform_create
就能实现这一点 —— serializer.save
的时候绑定 owner
为当前请求的用户。这个用户(self.request.user
)因为客户端通过 rest_auth
的 token
(或着是登录过之后 Set 下去的 session_id)在 django 的中间件处理1过之后已经是 Python 代码中的 User2 实例,所以相当于已经对 Snippet
model 中的 owner
赋了正确的值,而其它值在 POST 的 data 中,和修改前的行为一致。
TODO:关于
perform_create
3.2.2 retrieval of List View
List view 相对 Detail view 不太一样。
如果要限制用户只能看到当前属于他自己的数据,那么就意味着要 filter
出来 owner == 当前访问的用户
;只是添加 permission_classes = (IsOwner, )
不能达到目的。
为什么 permission_classes = (IsOwner, ) 行为不对?
正如上面所说的,对 List view 部分在 GET
的时候, 需要 filter 出来 own == <request.user>
:
class SnippetList(generics.ListCreateAPIView):
permission_classes = (IsOwner, ) # just for notice, not acturlly work
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def get_queryset(self):
user = self.request.user
return Snippet.objects.filter(owner=user)
如果是像 blog 这样的 model(应用),那么我们可能只是想限制 Detail View 的 not
SAFE_METHODS
3 操作,而 List View 的 Retrieval 不论从属,允许所有用户查看,那么只需要参照【DRF】Django REST Framework - Tutorial 4: 认证 和&权限 ? 简单添加permission_classes = [permissions.IsAuthenticatedOrReadOnly]
即可。
4. 关于更新 model(添加外键)后 migrate 数据
- 删除数据库重建。
- 添加的外键要含有
default
和null=True
,然后做 migrate,最后再删除null=True
(和default
)
Reference:
总结
结合 前言 的两个博客,一个 model 已经是完整的,可以上线使用的了。
- DRF 的
generic
提供了简单实现 REST API 的能力。 rest_auth
提供了限制访问 model 的能力。- 本文提供了 model 内的数据和 owner (通过外键)绑定的能力,以及精细访问权限到用户级别的能力(原本是有无登录级别)。