(生鲜项目)19. ViewSet实现商品详情页+热销商品+用户收藏+drf权限认证

第一步: 实现商品详情接口

1.商品详情页包括轮播图,详情,富文本

2.对于轮播图这种有多个值的外键, 应该再另外设计一个serializer表

3.写代码, 代码很简单

goods.views.py
# RetrieveModelMixin的功能是帮我们完成url的配置, /goods/id, 通过输入id就可以拿到某个商品的详情
class GoodsListViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
goods.serializers.py

class GoodsImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = GoodsImage
        fields = ("image",)

class GoodsSerializer(serializers.ModelSerializer):
    category = CategorySerializer()  
    images = GoodsImageSerializer(many=True)  # imags字段名不是随便设置的, 见下图
    class Meta:
        model = Goods
        fields = "__all__" 

4. models.Model中字段的的related_name属性有什么用?

 否则就报错, 下面就是写成 image 的后果 

 

5. 前端Vue联调

 

 

第二步: 热销商品接口

1.之前我们在设计models表的时候, 设计了一个 is_hot 字段, 所以要增加热卖商品的接口非常简单, 只需要在filter里把is_hot这个字段加进去就可以了

2.代码如下

goods.filters.GoodsFilter

class GoodsFilter(django_filters.rest_framework.FilterSet):
    ...
    fields = ['pricemin', 'pricemax','is_hot']
    ...

  3. API检查

  4. 前端Vue源码分析

 

  

第三步: 用户收藏接口

分析:

1.使用ViewSet作为url的逻辑处理函数, 收藏的功能应该在user_operation模块里面

2.添加收藏就是增加一条记录,所以要用CreateModelMixin, 取消收藏就是删除一条记录,所以要用DestroyModelMixin, 额外的我们也可以测试用户有哪些收藏,所以也需要ListModelMixin, 实现效果如下

3.配置url接口;

# 用户收藏接口(包含取消收藏,删除收藏)
router.register(r'userfavs', UserFavViewset, basename="userfavs")

4.Serializers里面的model应该使用user_operation.models.UserFav, 而且应该只有users和goods两个字段, 特殊的,如果前端要删除某一条收藏记录,我们需要返回id字段, 前端就方便使用

    

  5.这里有一个问题, 我们知道在测试这个接口的时候,REST肯定会提供多个user给我们选择(见下右图), 但逻辑上来说,这里我们应该使用的是登录的user才对, 所以这里要用到REST的Validator功能的高级设置  CurrentUserDefault 

当然, 要使用这个高级功能, 需要处于登录状态,否则报错没有user实例(下左图)

  

6.对于删除收藏,向后端发送请求的方式应该是delete, url类似于/userfavs/id, 图中的第2点现在已经不需要了, 我已经  把 SessionAuthentication.enforce_csrf()  注释掉了

 7.其实我们的设计有不合理的地方, 那就是添加收藏功能, 如果用户反复对某一商品进行收藏(见下图), 我们后端应该对其进行验证阻止添加重复的数据, 解决办法就是在Model的class Meta内添加 unique_together联合唯一,

 8.除了设置数据库,也可以使用REST的 UniqueTogetherValidator , 值得一提的是, serializer模块是可以识别到我们在model里面的unique_together的配置的,所以当出错时,是serializer模块封装的错误信息, 当然我们也可自定义错误提示信息

 

 代码:

user_operation.models.py

# 用户收藏
class UserFav(models.Model):
  ...
    class Meta:
    ...
        # 设置联合唯一,防止同一个用户对同一个商品反复收藏
        unique_together=("user","goods")
  ...

  views.py

from rest_framework import viewsets
from rest_framework import mixins

from .serializers import UserFavSerializer
from .models import UserFav


# 用户收藏功能
class UserFavViewset(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):
    queryset = UserFav.objects.all()
    serializer_class = UserFavSerializer

 serializers.py

from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator

from .models import UserFav


class UserFavSerializer(serializers.ModelSerializer):
    # 获取当前用户,并隐藏user字段
    user = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )

    class Meta:
        model = UserFav

        # 使用REST自己的联合唯一
        validators = [
            UniqueTogetherValidator(
                queryset=UserFav.objects.all(),
                fields=['user', 'goods'],
                message="不能重复收藏"
            )
        ]

        fields = ("user", "goods","id")  # 设置id字段是为了方便前端删除收藏

 设置联合唯一后, 如果对某一个商品反复收藏就会报错, 如下

 设置联合唯一, 并使用了REST自带的联合唯一后, 会有提示, 见下  non_field_errors代表多字段出错

 

 

第四步: drf权限验证

1.自己只能删除自己的收藏记录, 自己应该也只能看到自己的收藏记录, 这里就涉及到drf的权限验证, 这里我们要用到的是  IsAuthenticated   

 设置之后 

  

 2.当我们需要设置该用户只能删除自己的收藏信息时对, 需要用到  Custom permissions , 使用permissions,需要新建permissions文件, 当我们需要对返回的收藏信息进行过滤时, 即用户只能看到自己的收藏信息, 则需要重写queryset

   

 3.  因为1设置了要登录, 但是并不知道登录的用户是谁.  而全局没有设置token认证, 所以这里要加上JWT的token认证

  4. 使用admin的token  eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTc4MjA5NjU5LCJlbWFpbCI6IjFAMS5jb20ifQ.nPrSCklW0l85HANGKsWiwW8zBCJ8cgJs1eYz7KJFR3M

来发送删除收藏的请求

 

 

 

 5. 在检查是否登录的用户只能查看自己的收藏信息时, 发现无论怎么login都登录不上,

 

 这时因为我们刚刚在userfavs的逻辑里设置了只能通过JWT的token认证才能访问, 所以这里我们要把 SessionAuthentication 这种登录认证也加进来才行, 然后再登录就发现只返回了admin自己的登录信息, 到此所有功能均已经完成

 

 

 

 

 6.补充一个知识点, 那么就是  GenericAPIView  的  lookup_field 字段, 在配合  mixins.RetrieveModelMixin  使用时, 默认lookop_field = "pk", 如果我们需要能够通过get的方式访问到某一个good_id=15的属于admin用户的收藏记录, 那么就要如下配置

设置这个是因为前端发过来的参数就是userfav表的goods字段的的, 而不是pk这个字段

  

 7. 完整代码

serializers.py

from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator

from .models import UserFav


class UserFavSerializer(serializers.ModelSerializer):
    # 获取当前用户,并隐藏user字段
    user = serializers.HiddenField(
        default=serializers.CurrentUserDefault()
    )

    class Meta:
        model = UserFav

        # 使用REST自己的联合唯一
        validators = [
            UniqueTogetherValidator(
                queryset=UserFav.objects.all(),
                fields=['user', 'goods'],
                message="不能重复收藏"
            )
        ]

        fields = ("user", "goods","id")  # 设置id字段是为了方便前端删除收藏

 

views.py

from rest_framework import viewsets
from rest_framework import mixins
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.authentication import SessionAuthentication


from .serializers import UserFavSerializer
from .models import UserFav
from utils.permissions import IsOwnerOrReadOnly


# 用户收藏功能
class UserFavViewset(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin,
                     mixins.DestroyModelMixin, viewsets.GenericViewSet):

    # IsAuthenticated 用于验证用户是否登录
    # IsOwnerOrReadOnly 用户只能删除自己的收藏信息
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    serializer_class = UserFavSerializer

    # 可以通过 JWT的token认证登录, 也可以通过session登录
    authentication_classes = (JSONWebTokenAuthentication,SessionAuthentication)

    # 自定义搜索哪个字段
    lookup_field = "goods_id"

    # 只返回当前登录用户的收藏信息
    def get_queryset(self):
        return UserFav.objects.filter(user=self.request.user)

 

 utils,permissions.py

from rest_framework import permissions


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.user == request.user

 

 

 

第五步: Vue联调

1.前端传过来的就是goods:1, 所以刚刚我们设置的lookup_field刚好用上了

2. 更改收藏的url为local_host

 

 

 

 

 

 

---  君子处其实,不处其华;治其内,不治其外   张居正  ----

posted @ 2019-12-29 09:54  渱尘  阅读(413)  评论(0编辑  收藏  举报