drf的权限验证
看起来已经完成了用户添加收藏,删除收藏的功能。但是正常的业务逻辑应该是用户只能删除自己的收藏。
http://www.django-rest-framework.org/api-guide/permissions/
auth 和 permission是两种东西。auth是用来做用户验证的,permission是用来做权限判断的。
AllowAny:不管有没有权限都可以访问。
IsAuthenticated:判断是否已经登录
IsAdminUser:判断用户是否是一个管理员。
user.is_staff:第一步判断用户是否登录了。
实现代码:
from rest_framework.permissions import IsAuthenticated
permission_classes = (IsAuthenticated,)
用户未登录访问 userfav 的 list 会给我们抛出401的错误。
官方例子,ip 是否在白名单中
from rest_framework import permissions
class BlacklistPermission(permissions.BasePermission):
"""
Global permission check for blacklisted IPs.
"""
def has_permission(self, request, view):
ip_addr = request.META['REMOTE_ADDR']
blacklisted = Blacklist.objects.filter(ip_addr=ip_addr).exists()
return not blacklisted
拿着model做一下过滤实现
官方例子:是否是所有者,否则仅仅可读
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `owner` attribute.
"""
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
# Instance must have an attribute named `owner`.
return obj.owner == request.user
在utils中新建permissions,这是我们自定义的permissions,然后粘贴上面的IsOwnerOrReadOnly
这个自定义的 permission 类继承了我们的BasePermission。它有一个方法叫做has_object_permission,是否有对象权限。
会检测我们从数据库中拿出来的obj的owner是否等于request.user
这个obj是我们数据库中的表,所以这里的owner应该改为我们数据库中的外键user
安全的方法也就是不会对数据库进行变动的方法,总是可以让大家都有权限访问到。
views中添加该自定义的权限认证类
from utils.permissions import IsOwnerOrReadOnly
permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
这样在做删除的时候就会验证权限。
不能让所有的收藏关系数据都被获取到。因此我们要重载get_queryset方法
def get_queryset(self):
return UserFav.objects.filter(user=self.request.user)
重载之后queryset的参数配置就可以注释掉了
在model设计中。我们的str方法返回值为name。这个name是有可能为null的。
在使用其他工具时输入用户名密码也可以进行登录,是因为我们配置了多种auth类。
token的认证最好是配置到view里面去,而不是配置到全局中。
前端的每一个request请求都加入我们的token的话,token过期了,当用户访问goods列表页等不需要token认证的页面也会拿不到数据。
将setting中的’rest_framework.authentication.SessionAuthentication’,删除掉。
然后在具体的view中来import以及进行配置
user_operation/views.py中实现代码:
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
authentication_classes = (JSONWebTokenAuthentication, )
此时在我们的api控制台以及无法使用登录进入userfav了,是因为我们的类内auth并不包含session auth
from rest_framework.authentication import SessionAuthentication
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
drf的api文档自动生成
文档的功能其实我们之前已经配置过了。
文档功能的url配置后面是不能加$符号结尾的。
# DRF自动文档, 方便前后端交互的文档
url(r'docs/', include_docs_urls(title="DRF SHOP DOCS")),
优点: 全自动生成,文档里面做测试与交互。生成js shell代码段
左侧下方有个source code 可以选择shell JavaScript python
每一个接口会写好示例代码,把需要传递的参数说明清楚。
会将username和password这种参数写好。
根据我们在urls中配置的url自动生成文档。
动态设置Serializer和permission获取用户信息
首先完成的功能,用户个人信息的修改。手机号码是不可编辑的,我们在注册的时候对于手机号码进行了验证。
点击用户资料,首先要将已有资料进行显示,所以要有一个接口请求用户的个人资料
UserViewset 接收post方法时用于用户注册,这时候我们只需要重载retrieveModelMixin
from rest_framework import mixins
class UserViewset(CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
users/views.py
中实现代码:
# 重写该方法,不管传什么id,都只返回当前用户
def get_object(self):
return self.request.user
restful api 是对资源的操作,用户注册的时候post是对用户资源的一个操作
获取用户信息是getdetail 修改个人信息是update
直接使用UserViewset进行重用。
既然要获取到当前的用户,必须是一种登录的状态。
permission_classes = (permissions.IsAuthenticated, )
访问viewset的方法时,都必须用户登录才可以。
但是create方法用户注册时不可能放在permission验证通过之后的。
这样我们就希望这个permission能在不同的http请求方式面前进行动态的选择
用户注册的时候没有权限,用户在get时有权限验证。
为了解决问题研究源码
rest_framework/views.py
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]
在我们之前配置的permission class里面去遍历,返回permission class的一个实例也就是对象
实际上我们就可以重载这个函数进行返回。问题有来了,我们的post get请求对应的action是什么?
与函数名称是保持一致的。action放在self中,只有viewset是这样的,如果使用的是api view就不会这样了。
def get_permissions(self):
if self.action == "retrieve":
return [permissions.IsAuthenticated()]
elif self.action == "create":
return []
return []
retrieve和create进行单独的处理。其他情况均返回空
既然要用户认证,此时我们的auth class被全局删除了之后,在用户注册中还没有配置
authentication_classes = (JSONWebTokenAuthentication, authentication.SessionAuthentication )
用户认证弹出框的模式是basic auth的模式,现在配置的这两种模式实际上是不需要用户输入用户名和密码的。
浏览器中添加session或head中添加token的。
刚才弹出是因为我们在setting中做过的设置
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
只返回了用户名和mobile,是因为我们之前设置了Serializer
fields = (“username”, “code”, “mobile”, “password”)
四个字段只有其中的username 和 mobile会显示,因为其他两个字段是writeonly
我们希望获取用户详情的时候,使用一个另外的Serializer
class UserDetailSerializer(serializers.ModelSerializer):
"""
用户详情序列化
"""
class Meta:
model = User
fields = ("username", "gender", "birthday", "email","mobile")
问题来了,注册时候如果使用userdetailSerializer会导致那些没有的字段验证失败。
我们如何跟permission一样,动态的使用Serializer呢?
Serializer class 位于genericAPIView中
Serializer_class 可以通过直接配置,也可以通过get_Serializer_class函数获取到
所以我们需要动态的选择Serializer就可以重载这个函数就可以了
users/views.py
中实现代码:
def get_serializer_class(self):
if self.action == "retrieve":
return UserDetailSerializer
elif self.action == "create":
return UserRegSerializer
return UserDetailSerializer
用户个人信息修改
为UserViewset配置update mixins
这里面承载了更新和部分更新的操作。
它接受put和patch请求,put实际是一种更新的操作,patch是部分更新。
用户收藏功能
我们之前在UserFavViewSet中的Listmodelmixin已经将goods的id 和 这条收藏关系的id返回了。但是我们需要的是商品的信息,所以在Serializer中
添加一个Serializers
class UserFavDetailSerializer(serializers.ModelSerializer):
# 通过goods_id拿到商品信息。就需要嵌套的Serializer
goods = GoodsSerializer()
class Meta:
model = UserFav
fields = ("goods", "id")
收藏提交的时候只需要传递goods的id过来,userfavdetail获取的时候想要获取goods的详情。
动态的Serializer。
# 设置动态的Serializer
def get_serializer_class(self):
if self.action == "list":
return UserFavDetailSerializer
elif self.action == "create":
return UserFavSerializer
return UserFavSerializer
用户留言功能
删除,获取留言,添加用户留言。留言中可以上传文件。
后台接口viewset和配套的Serializer
user_operation/serializers.py:
class LeavingMessageSerializer(serializers.ModelSerializer):
"""
用户留言序列化
"""
user = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
# 设置当天提交的时间 ready_only=True, 只返回不提交
add_time = serializers.DateTimeField(read_only=True, format="%Y-%m-%d %H:%M:%S")
class Meta:
model = UserLeavingMessage
fields = ("user", "message_type", "subject", "message", "add_time", "file", "id")
user直接获取当前的用户,add_time设置只可读取,格式化。
class LeavingMessageViewSet(mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
viewsets.GenericViewSet):
"""
list: 获取用户留言
create: 添加留言
delete: 删除留言
"""
# 需要登录状态与用户验证
permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
serializer_class = LeavingMessageSerializer
def get_queryset(self):
return UserLeavingMessage.objects.filter(user=self.request.user)
配置 url
# 配置用户留言的url
router.register(r'messages', LeavingMessageViewset, base_name="messages"),
我们在删除的时候需要服务器给我们返回的 id。前后端分离的系统,服务器返回完整地址可以直接被前端查看。
addtime 应该是只返回不提交。由获取的当前时间自动填充。
用户收货地址列表页接口开发
收货地址也是放在user_operation中的。获取到所有的收货地址,修改某一个收货地址。
删除某一个收货地址。
需要列表,添加,更新,删除:继承增删改查的mixin,但是如果都需要,那么有有一个viewset已经帮我们做到了。
我们只需要继承于ModelViewSet就行了。
添加一个 Serializer
class AddressSerializer(serializers.ModelSerializer):
"""
收货地址序列化
"""
user = serializers.HiddenField(
default=serializers.CurrentUserDefault
)
add_time = serializers.DateTimeField(read_only=True, format="%Y-%m-%d %H:%M:%S")
def validated_mobile(self, signer_mobile):
if not re.match(REGEX_MOBILE, signer_mobile):
raise serializers.ValidationError("手机号非法")
return signer_mobile
class Meta:
model = UserAddress
# fields = "__all__"
fields = ("id", "add_time", "user", "province", "city", "district",
"address", "signer_name", "signer_mobile")
地址回填的时候,如果是一个地址要将它的省市区全部回填就要拆分出来,这样比较麻烦。所以我们设置三个字段进行存储。省 市 区域
viewset 中进行配置
class AddressViewSet(viewsets.ModelViewSet):
"""
收获地址管理
list: 获取收货地址
create: 添加收货地址
update: 更新收货地址
delete: 删除收货地址
"""
permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
serializer_class = AddressSerializer
def get_queryset(self):
return UserAddress.objects.filter(user=self.request.user)
在url中进行配置:
# 收货地址
router.register(r'address', , base_name="address"),