Python Web开发学习Django+Vue生鲜电商

【Vue+DRF生鲜电商】19.用户添加、删除收藏权限处理,根据

2019-05-30  本文已影响0人  吾星喵

更多内容请点击 我的博客 查看,欢迎来访。

用户添加、删除收藏记录权限问题

如果用户指定id删除收藏记录,如果这条收藏记录是其他人的,而不是当前登录用户的,仍然会被成功删除掉,这时候就涉及到一个权限问题:用户只能查看到自己的收藏记录,且只能删除自己的收藏记录。

需要登录管理收藏记录IsAuthenticated

访问 https://www.django-rest-framework.org/api-guide/permissions/#isauthenticated 参考IsAuthenticated

IsAuthenticated权限类将拒绝任何未经身份验证的用户的权限,否则将允许权限。

如果您希望您的API只允许注册用户访问,则此权限是合适的。

下面使用IsAuthenticated权限来限制只有用户登录后才能访问。

# apps/user_operation/views.py


from rest_framework.permissions import IsAuthenticated


class UserFavViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    用户收藏商品
    取消收藏商品
    显示收藏商品
    """
    queryset = UserFav.objects.all()
    serializer_class = UserFavSerializer
    permission_classes = (IsAuthenticated,)

BLOG_20190530_170858_85

当退出登录用户后,如果再访问 http://127.0.0.1:8000/userfavs/ 则会提示401错误。

但仅有IsAuthenticated权限是不够的,删除收藏记录还要判断这条记录的创建人是否是当前登录的用户,如果是才允许删除。

DRF自定义BasePermission验证所有者

DRF也提供了自定义权限功能,要实现自定义权限,覆盖BasePermission并实现以下方法之一或两者都实现:

如果应该授予请求访问权,则方法应该返回True,否则返回False

如果鉴权失败,自定义权限将引发一个PermissionDenied异常。要更改与异常关联的错误消息,直接在自定义权限上实现message属性。否则,将使用PermissionDenied中的default_detail属性。

可以查看 https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions 的示例

在util文件夹下创建 permissions.py 文件,用户放置权限相关的配置。

# utils/permissions.py

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    对象级权限,只允许对象的所有者编辑它。
    模型实例有一个 user 属性,指向用户的外键。
    """

    def has_object_permission(self, request, view, obj):
        # 读取权限允许任何请求,所以我们总是允许GET、HEAD或OPTIONS请求。
        if request.method in permissions.SAFE_METHODS:
            return True

        # 实例必须有一个名为user的属性。
        return obj.user == request.user

以上的意思就是只有当对象的user和当前登录user一样,才返回True,表明鉴权通过。

然后再用户收藏ViewSet中添加该权限类

# apps/user_operation/views.py

from utils.permissions import IsOwnerOrReadOnly

class UserFavViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    用户收藏商品
    取消收藏商品
    显示收藏商品
    """
    queryset = UserFav.objects.all()
    serializer_class = UserFavSerializer
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)

只能查看自己的收藏记录

还有一个问题就是,获取用户收藏时,不能获取所有的收藏记录,只获取到当前用户的收藏记录。所以要重载get_queryset()方法,过滤当前用户。

# apps/user_operation/views.py

class UserFavViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    用户收藏商品
    取消收藏商品
    显示收藏商品
    """
    queryset = UserFav.objects.all()
    serializer_class = UserFavSerializer
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)

    def get_queryset(self):
        # 过滤当前用户的收藏记录
        return self.queryset.filter(user=self.request.user)

现在访问 http://127.0.0.1:8000/userfavs/ 给当前用户增加几个收藏

BLOG_20190530_170846_74

在后台将任意一个收藏记录修改为其他用户,比如这儿id=5的, http://127.0.0.1:8000/admin/user_operation/userfav/5/change/ 改为另一个用户

BLOG_20190530_170839_35

现在序列化时只会显示当前登录用户的收藏记录了。

测试取消全局登录认证

BLOG_20190530_170829_91

现在退出当前登录用户,最好重新打开浏览器,然后用工具请求删除功能,比如删除id=4的收藏记录

BLOG_20190530_170822_98

点击send之后,因为权限类中配置了IsAuthenticated,就会弹出登录框,取消就会出现401的错误。如果如果帐密后,那么该记录就会被删除

BLOG_20190530_170816_91

登陆之后,该记录就会被成功删除

BLOG_20190530_170809_39

刷新 http://127.0.0.1:8000/userfavs/ 指定id的记录就消失了。

BLOG_20190530_170801_45

这儿直接可以输入账号密码就完成登录,因为在 settings.py 中REST_FRAMEWORK配置了'rest_framework.authentication.BasicAuthentication',(即输入用户名密码认证模式)

注释掉全局token认证

# DjangoOnlineFreshSupermarket/settings.py


REST_FRAMEWORK = {
    # 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    # 'PAGE_SIZE': 5,
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',  # 上面两个用于DRF基本验证
        # 'rest_framework.authentication.TokenAuthentication',  # TokenAuthentication,取消全局token,放在视图中进行
        # 'rest_framework_simplejwt.authentication.JWTAuthentication',  # djangorestframework_simplejwt JWT认证
    )
}

token认证最好是放在view里面去,不要配置在全局变量中,如果再前端请求中,每一个request都加入token,这个token恰好过期了,那么用户在访问category、goods这种公开数据时,就会抛异常,连商品的列表页都访问不了了。

配置局部JWTAuthentication认证

需要将JWTAuthentication配置到需要的view中,这才是一种比较安全的做法,比如在用户收藏ViewSet中配置

# apps/user_operation/views.py

from rest_framework_simplejwt.authentication import JWTAuthentication

class UserFavViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    用户收藏商品
    取消收藏商品
    显示收藏商品
    """
    queryset = UserFav.objects.all()
    serializer_class = UserFavSerializer
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    authentication_classes = (JWTAuthentication,)  # 配置登录认证

    def get_queryset(self):
        # 过滤当前用户的收藏记录
        return self.queryset.filter(user=self.request.user)

这样就不会在全局做token验证了。用户在发送JWT token时,如果是GoodsListViewSet,那么商品列表视图就不会调用这个JWTAuthentication,不会抛异常。只有当访问UserFavViewSet这种配置了authentication_classes,才会进行token验证。

现在来删除id=3的收藏数据,会出现401错误,表示需要用户登录

BLOG_20190530_170748_46

熟练需要获取token,将用户名密码提交到 http://127.0.0.1:8000/login/ 获取

BLOG_20190530_170743_91

可以得到

{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTU1ODUxOTIyNSwianRpIjoiOGE1MWY4NmNlMTZlNDZmMjk4ODJmZmU0ZjExNjE0ODciLCJ1c2VyX2lkIjoxfQ.t31c-RiD3mjq3Fb3YvvDhZi_Yd5vcQl8f4OfTU0_oLE",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTU3ODI4MDI1LCJqdGkiOiJiYTEzMDYzNjMxNTU0NzFkYTE3ODNlN2FmYWMwODhiNCIsInVzZXJfaWQiOjF9.L_ZmXyv0wti9HImWDM5qcHVU7LTwBO--7JFGLb7l9rU"
}

然后通过这个token来请求,需要添加Header,键为Authorization,值为Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNTU3ODI4MDI1LCJqdGkiOiJiYTEzMDYzNjMxNTU0NzFkYTE3ODNlN2FmYWMwODhiNCIsInVzZXJfaWQiOjF9.L_ZmXyv0wti9HImWDM5qcHVU7LTwBO--7JFGLb7l9rU

BLOG_20190530_170735_13

就可以成功删除记录。

如果不是删除当前用户创建的记录,则会提示404错误

BLOG_20190530_170728_37

配置局部SessionAuthentication认证

访问 http://127.0.0.1:8000/userfavs/ 并点击右上角的登录

BLOG_20190530_170721_18

但仍然会显示"detail": "身份认证信息未提供。",因为在authentication_classes = (JWTAuthentication,)中只使用了JWTAuthentication,表明只支持JWT用户认证,如果想要DRF Api中可以通过认证,还需要添加SessionAuthentication,表明支持session认证模式。

# apps/user_operation/views.py

from rest_framework.authentication import SessionAuthentication

class UserFavViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet):
    """
    用户收藏商品
    取消收藏商品
    显示收藏商品
    """
    queryset = UserFav.objects.all()
    serializer_class = UserFavSerializer
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    authentication_classes = (JWTAuthentication, SessionAuthentication)  # 配置登录认证:支持JWT认证和DRF基本认证

    def get_queryset(self):
        # 过滤当前用户的收藏记录
        return self.queryset.filter(user=self.request.user)

BLOG_20190530_170711_77

显示具体收藏,根据商品id

首先将ViewSet继承mixins.RetrieveModelMixin

UserFavViewSet继承的viewsets.GenericViewSet又继承了generics.GenericAPIView,其中有一个lookup_field = 'pk'属性(如果想使用pk以外的对象查找,设置lookup_field

访问 https://www.django-rest-framework.org/api-guide/generic-views/#genericapiview 可以查看相关的属性

以下属性控制着基本视图的行为。

将项目的lookup_field修改为商品的id,因为对于当前登录用户来说,用户收藏商品是唯一的,它是根据get_queryset(self)筛选后搜索的。

# apps/user_operation/views.py


class UserFavViewSet(mixins.CreateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    """
    用户收藏商品
    取消收藏商品
    显示收藏商品列表
    根据商品id显示收藏详情
    """
    queryset = UserFav.objects.all()
    serializer_class = UserFavSerializer
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    authentication_classes = (JWTAuthentication, SessionAuthentication)  # 配置登录认证:支持JWT认证和DRF基本认证
    lookup_field = 'goods_id'

    def get_queryset(self):
        # 过滤当前用户的收藏记录
        return self.queryset.filter(user=self.request.user)

测试下是否是根据goods_id搜索

如果直接搜索id,则搜索不到结果

BLOG_20190530_170659_58

BLOG_20190530_170654_19

就可以按照商品的id显示详情。可用性高,用户进入商品详情页,用户要去查询这个商品有没有被收藏,这个url里面就可以直接填写goods的id,不需要知道当时数据库里面保存的数据id是什么。直接根据商品的id去查询收藏,如果有记录,则页面就显示已收藏的信息,如果没被记录,表明商品没被收藏。

这里按照goods.id进行查找,对于同一个商品,可能会被很多人收藏,但并不会造成查询出错,因为商品和用户一起构成了唯一性,而且lookup_field字段是在get_queryset(self)过滤之后的结果进行查找,也就是过滤当前登录用户,保证了唯一性,所以不会出错。

Vue联调

在商品详情页,如果用户没有登录的情况,那么收藏按钮的状态应该是未收藏。

BLOG_20190530_170647_64

这时候是无法获取用户的。Vue逻辑如下

获取商品收藏状态

用户进入详情页,也就是 src/views/productDetail/productDetail.vue 组件,获取商品的id,从cookie中找token

// src/views/productDetail/productDetail.vue


        created() {
            this.productId = this.$route.params.productId;
            var productId = this.productId;
            if (cookie.getCookie('token')) {
                getFav(productId).then((response) => {
                    this.hasFav = true
                }).catch(function (error) {
                    console.log(error);
                });
            }
            this.getDetails();
        },

如果找到这个token,就调用getFav(productId)查询该商品是否被收藏。这里面调用请求后端的接口。

// src/api/api.js


//判断是否收藏
export const getFav = goodsId => {
    return axios.get(`${local_host}/userfavs/` + goodsId + '/')
};

如果获取的状态码为200,也就是已收藏状态(因为未获取到状态码为404),则将hasFav设置为true,看下面的判断逻辑

<!-- src/views/productDetail/productDetail.vue -->

<a v-if="hasFav" id="fav-btn" class="graybtn" @click="deleteCollect">
    <i class="iconfont">&#xe613;</i>已收藏</a>
<a v-else class="graybtn" @click="addCollect">
    <i class="iconfont">&#xe613;</i>收藏</a>

如果为true,则显示已收藏,否则显示收藏。

删除收藏点击

用户点击已收藏按钮时,则会执行@click="deleteCollect"操作

// src/views/productDetail/productDetail.vue

            deleteCollect() {
                //删除收藏
                delFav(this.productId).then((response) => {
                    //console.log(response.data);
                    this.hasFav = false
                }).catch(function (error) {
                    console.log(error);
                });
            },

在这个方法中,调用删除收藏的接口,如果删除成功,状态码也是204,则将hasFav置为false

// src/api/api.js

//取消收藏
export const delFav = goodsId => {
    return axios.delete(`${local_host}/userfavs/` + goodsId + '/')
};

添加收藏点击

如果商品未收藏,用户点击该按钮时,执行@click="addCollect"

// src/views/productDetail/productDetail.vue

            addCollect() { //加入收藏
                addFav({
                    goods: this.productId
                }).then((response) => {
                    //console.log(response.data);
                    this.hasFav = true;
                    alert('已成功加入收藏夹');
                }).catch(function (error) {
                    console.log(error);
                });
            },

这里面会调用收藏接口

// src/api/api.js

//收藏
export const addFav = params => {
    return axios.post(`${local_host}/userfavs/`, params)
};

向后台POST{goods: this.productId},如果状态码为2xx,则收藏成功。hasFav置为true

登录之后打开调试,然后点击收藏

BLOG_20190530_170633_71

会收到弹框提示。按钮就变为已收藏了。

BLOG_20190530_170627_33

然后取消收藏,状态码为204

BLOG_20190530_170621_82

刷新 http://127.0.0.1:8000/admin/user_operation/userfav/ 之前的收藏已经消失了。

BLOG_20190530_170616_80
上一篇下一篇

猜你喜欢

热点阅读