您现在的位置是: 网站首页 >Django >Vue+Django REST framework前后端分离生鲜电商 Django

【Vue+DRF生鲜电商】19.用户添加、删除收藏权限处理,根据商品id显示收藏,在Vue中实现收藏功能

admin2019年5月30日 17:03 Django | Vue 1376人已围观

Vue+Django REST framework前后端分离生鲜电商简介 Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。 Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket ; Django版本:2.2、djangorestframework:3.9.2。 前端Vue模板可以直接联系我拿。

### 用户添加、删除收藏记录权限问题 如果用户指定id删除收藏记录,如果这条收藏记录是其他人的,而不是当前登录用户的,仍然会被成功删除掉,这时候就涉及到一个权限问题:用户只能查看到自己的收藏记录,且只能删除自己的收藏记录。 #### 需要登录管理收藏记录IsAuthenticated 访问 https://www.django-rest-framework.org/api-guide/permissions/#isauthenticated 参考`IsAuthenticated` `IsAuthenticated`权限类将拒绝任何未经身份验证的用户的权限,否则将允许权限。 如果您希望您的API只允许注册用户访问,则此权限是合适的。 下面使用`IsAuthenticated`权限来限制只有用户登录后才能访问。 ```python # 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](/media/blog/images/2019/05/BLOG_20190530_170858_85.png "博客图集BLOG_20190530_170858_85.png") 当退出登录用户后,如果再访问 http://127.0.0.1:8000/userfavs/ 则会提示401错误。 但仅有`IsAuthenticated`权限是不够的,删除收藏记录还要判断这条记录的创建人是否是当前登录的用户,如果是才允许删除。 #### DRF自定义BasePermission验证所有者 DRF也提供了自定义权限功能,要实现自定义权限,覆盖`BasePermission`并实现以下方法之一或两者都实现: - `.has_permission(self, request, view)` - `.has_object_permission(self, request, view, obj)` 如果应该授予请求访问权,则方法应该返回`True`,否则返回`False`。 如果鉴权失败,自定义权限将引发一个`PermissionDenied`异常。要更改与异常关联的错误消息,直接在自定义权限上实现`message`属性。否则,将使用`PermissionDenied`中的`default_detail`属性。 可以查看 https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions 的示例 在util文件夹下创建 permissions.py 文件,用户放置权限相关的配置。 ```python # 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中添加该权限类 ```python # 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()`方法,过滤当前用户。 ```python # 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](/media/blog/images/2019/05/BLOG_20190530_170846_74.png "博客图集BLOG_20190530_170846_74.png") 在后台将任意一个收藏记录修改为其他用户,比如这儿`id=5`的, http://127.0.0.1:8000/admin/user_operation/userfav/5/change/ 改为另一个用户 ![BLOG_20190530_170839_35](/media/blog/images/2019/05/BLOG_20190530_170839_35.png "博客图集BLOG_20190530_170839_35.png") 现在序列化时只会显示当前登录用户的收藏记录了。 #### 测试取消全局登录认证 ![BLOG_20190530_170829_91](/media/blog/images/2019/05/BLOG_20190530_170829_91.png "博客图集BLOG_20190530_170829_91.png") 现在退出当前登录用户,最好重新打开浏览器,然后用工具请求删除功能,比如删除`id=4`的收藏记录 ![BLOG_20190530_170822_98](/media/blog/images/2019/05/BLOG_20190530_170822_98.png "博客图集BLOG_20190530_170822_98.png") 点击send之后,因为权限类中配置了`IsAuthenticated`,就会弹出登录框,取消就会出现401的错误。如果如果帐密后,那么该记录就会被删除 ![BLOG_20190530_170816_91](/media/blog/images/2019/05/BLOG_20190530_170816_91.png "博客图集BLOG_20190530_170816_91.png") 登陆之后,该记录就会被成功删除 ![BLOG_20190530_170809_39](/media/blog/images/2019/05/BLOG_20190530_170809_39.png "博客图集BLOG_20190530_170809_39.png") 刷新 http://127.0.0.1:8000/userfavs/ 指定id的记录就消失了。 ![BLOG_20190530_170801_45](/media/blog/images/2019/05/BLOG_20190530_170801_45.png "博客图集BLOG_20190530_170801_45.png") 这儿直接可以输入账号密码就完成登录,因为在 settings.py 中`REST_FRAMEWORK`配置了`'rest_framework.authentication.BasicAuthentication',`(即输入用户名密码认证模式) 注释掉全局token认证 ```python # 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中配置 ```python # 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](/media/blog/images/2019/05/BLOG_20190530_170748_46.png "博客图集BLOG_20190530_170748_46.png") 熟练需要获取token,将用户名密码提交到 http://127.0.0.1:8000/login/ 获取 ![BLOG_20190530_170743_91](/media/blog/images/2019/05/BLOG_20190530_170743_91.png "博客图集BLOG_20190530_170743_91.png") 可以得到 ```json { "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](/media/blog/images/2019/05/BLOG_20190530_170735_13.png "博客图集BLOG_20190530_170735_13.png") 就可以成功删除记录。 如果不是删除当前用户创建的记录,则会提示404错误 ![BLOG_20190530_170728_37](/media/blog/images/2019/05/BLOG_20190530_170728_37.png "博客图集BLOG_20190530_170728_37.png") #### 配置局部SessionAuthentication认证 访问 http://127.0.0.1:8000/userfavs/ 并点击右上角的登录 ![BLOG_20190530_170721_18](/media/blog/images/2019/05/BLOG_20190530_170721_18.png "博客图集BLOG_20190530_170721_18.png") 但仍然会显示`"detail": "身份认证信息未提供。"`,因为在`authentication_classes = (JWTAuthentication,)`中只使用了`JWTAuthentication`,表明只支持JWT用户认证,如果想要DRF Api中可以通过认证,还需要添加`SessionAuthentication`,表明支持session认证模式。 ```python # 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](/media/blog/images/2019/05/BLOG_20190530_170711_77.png "博客图集BLOG_20190530_170711_77.png") ### 显示具体收藏,根据商品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 可以查看相关的属性 以下属性控制着基本视图的行为。 - `queryset`:用于从视图返回对象的查询结果集。通常,你必须设置此属性或者重写 `get_queryset()` 方法。如果你重写了一个视图的方法,重要的是你应该调用 `get_queryset()` 方法而不是直接访问该属性,因为 `queryset` 将被计算一次,这些结果将为后续请求缓存起来。 - `serializer_class`:用于验证和反序列化输入以及用于序列化输出的Serializer类。 通常,你必须设置此属性或者重写`get_serializer_class()` 方法。 - `lookup_field` :用于执行各个model实例的对象查找的model字段。默认为 `'pk'`。 请注意,在使用超链接API时,如果需要使用自定义的值,你需要确保在API视图*和*序列化类*都*设置查找字段。 - `lookup_url_kwarg` :应用于对象查找的URL关键字参数。它的 URL conf 应该包括一个与这个值相对应的关键字参数。如果取消设置,默认情况下使用与 `lookup_field`相同的值。 将项目的`lookup_field`修改为商品的id,因为对于当前登录用户来说,用户收藏商品是唯一的,它是根据`get_queryset(self)`筛选后搜索的。 ```python # 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](/media/blog/images/2019/05/BLOG_20190530_170659_58.png "博客图集BLOG_20190530_170659_58.png") ![BLOG_20190530_170654_19](/media/blog/images/2019/05/BLOG_20190530_170654_19.png "博客图集BLOG_20190530_170654_19.png") 就可以按照商品的id显示详情。可用性高,用户进入商品详情页,用户要去查询这个商品有没有被收藏,这个url里面就可以直接填写goods的`id`,不需要知道当时数据库里面保存的数据id是什么。直接根据商品的id去查询收藏,如果有记录,则页面就显示已收藏的信息,如果没被记录,表明商品没被收藏。 这里按照`goods.id`进行查找,对于同一个商品,可能会被很多人收藏,但并不会造成查询出错,因为商品和用户一起构成了唯一性,而且`lookup_field`字段是在`get_queryset(self)`过滤之后的结果进行查找,也就是过滤当前登录用户,保证了唯一性,所以不会出错。 ### Vue联调 在商品详情页,如果用户没有登录的情况,那么收藏按钮的状态应该是未收藏。 ![BLOG_20190530_170647_64](/media/blog/images/2019/05/BLOG_20190530_170647_64.png "博客图集BLOG_20190530_170647_64.png") 这时候是无法获取用户的。Vue逻辑如下 #### 获取商品收藏状态 用户进入详情页,也就是 src/views/productDetail/productDetail.vue 组件,获取商品的id,从cookie中找`token` ```JavaScript // 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)`查询该商品是否被收藏。这里面调用请求后端的接口。 ```JavaScript // src/api/api.js //判断是否收藏 export const getFav = goodsId => { return axios.get(`${local_host}/userfavs/` + goodsId + '/') }; ``` 如果获取的状态码为200,也就是已收藏状态(因为未获取到状态码为404),则将`hasFav`设置为`true`,看下面的判断逻辑 ```html <!-- 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"`操作 ```JavaScript // 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` ```JavaScript // src/api/api.js //取消收藏 export const delFav = goodsId => { return axios.delete(`${local_host}/userfavs/` + goodsId + '/') }; ``` #### 添加收藏点击 如果商品未收藏,用户点击该按钮时,执行`@click="addCollect"` ```JavaScript // 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); }); }, ``` 这里面会调用收藏接口 ```JavaScript // 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](/media/blog/images/2019/05/BLOG_20190530_170633_71.png "博客图集BLOG_20190530_170633_71.png") 会收到弹框提示。按钮就变为已收藏了。 ![BLOG_20190530_170627_33](/media/blog/images/2019/05/BLOG_20190530_170627_33.png "博客图集BLOG_20190530_170627_33.png") 然后取消收藏,状态码为204 ![BLOG_20190530_170621_82](/media/blog/images/2019/05/BLOG_20190530_170621_82.png "博客图集BLOG_20190530_170621_82.png") 刷新 http://127.0.0.1:8000/admin/user_operation/userfav/ 之前的收藏已经消失了。 ![BLOG_20190530_170616_80](/media/blog/images/2019/05/BLOG_20190530_170616_80.png "博客图集BLOG_20190530_170616_80.png")

很赞哦! (1)

文章交流

  • emoji
0人参与,0条评论

当前用户

未登录,点击   登录

站点信息

  • 建站时间:网站已运行2075天
  • 系统信息:Linux
  • 后台程序:Python: 3.8.10
  • 网站框架:Django: 3.2.6
  • 文章统计:256 篇
  • 文章评论:60 条
  • 腾讯分析网站概况-腾讯分析
  • 百度统计网站概况-百度统计
  • 公众号:微信扫描二维码,关注我们
  • QQ群:QQ加群,下载网站的学习源码
返回
顶部
标题 换行 登录
网站