(生鲜项目_用户板块)21. 用户信息+我的收藏+用户留言+收货地址
第一步: 用户信息
1.前端在进入到更改用户信息的界面, 会向后端索取用户的各种信息, 但是前端并不知道用户当前的id是多少, 而且后端之前也没配置get请求的接口, 所以配置如下
重写get_object后, 无论前端发过来什么数据 例如 /users/999/ , 后端都可以返回给前端当前用户的信息
class UserViewset(CreateModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet): ... # 给retrieve用的, 目的是给前端返回当前登录用户信息 def get_object(self): return self.request.user ...
2. 既然前端要获取用户登录的信息, 那么肯定前端发过来的请求要携带登录的信息,要么是session,要么是JWT token, 所以后端应该设置
permission_classes = (permissions.IsAuthenticated,)
但这么配置会导致一个问题, 会使我们在创建用户的时候也被迫要求处于登录状态, 这不是自相矛盾吗? 所以我们要动态的配置permission_classes, 不同的请求方式用不同的permission_classes
办法就是重写 APIView中的 get_permissions 方法(该方法原本的目的是遍历permission_classes, 并一个个返回) 源码如下
这里, 我们重写它, 当然配套的你还得指定认证方式, 这里设置JWT和session两种
class UserViewset(...)
...
# 登录的方式,session是为了浏览器验证方便 authentication_classes = (JSONWebTokenAuthentication,authentication.SessionAuthentication) # 动态配置permission_classes, 如果是create就不需要登录, 否则就要求登录 def get_permissions(self): if self.action == "retrieve": # 只有viewSet才能使用action的属性 return [permissions.IsAuthenticated()] # 返回实例 elif self.action == "create": return [] return []
3. 使用文档测试一下接口
4. 很明显, 因为我们之前的设置, (下图), 所以只返回这两个字段, 如果要返回其它字段信息, 我们不得不重写一个serializer,
并且在viewSet里面动态使用serializer, 即重写GenericAPIView的get_serializer_class, 和重写get_permissions逻辑差不多
users.serializers.py
# 用户详情的序列化类 class UserDetailSerializer(serializers.ModelSerializer): """ 用户详情的序列化类 """ class Meta: model = User fields = ("name","gender","birthday","email","mobile")
views.py
# 动态配置serializer_class, 如果是create就用UserRegSerializer, 否则就用UserDetailSerializer def get_serializer_class(self): if self.action == "retrieve": # 只有viewSet才能使用action的属性 return UserDetailSerializer elif self.action == "create": return UserRegSerializer return UserDetailSerializer
使用文档去测试接口, 成功返回了想要的字段
第二步: Vue联调
1.前端源码
2.前端测试, 邮箱已经正常显示, 说明没问题了
第三步: 个人信息修改
1.前面我们已经完成了很多技术难点, 现在只需要新增 mixins.UpdateModelMixin 即可, update插件里面其实可以实现全部更新(put请求方式)和部分更新(patch请求方式)
class UserViewset(CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
2. 去文档测试, 可发现多出来两项设置, 重新填了token后(刷新页面, 文档的token就会丢失),
3.去前端页面测试修改, 首先更改Vue源码, 这里就能看到是patch请求方式
第四步: 我的收藏
1.目前, 我们对userFav接口的返回数据见下, 只有goods_id, 这是不够的
2. 为了返回good的全部信息, 我们就需要向前面返回用户信息一样, 单独设计一个 UserDetailSerializer , 并给其增加goods字段(继承自GoodsSerializer)
user_operation.serializers.py
# 返回用户收藏中的good的详细信息 class UserFavDetailSerializer(serializers.ModelSerializer): goods = GoodsSerializer() # 新增goods字段,并继承goods的serializer class Meta: model = UserFav fields = ("goods", "id")
views.py
class UserFavViewset(...) ... # 动态配置serializer_class, 如果是list就用UserFavDetailSerializer, 否则就用UserFavSerializer def get_serializer_class(self): if self.action == "list": # 只有viewSet才能使用action的属性 return UserFavDetailSerializer elif self.action == "create": return UserFavSerializer return UserFavSerializer ...
3. 去API界面测试, 可看到goods的字段全部都返回了
4. 前端Vue源码解读
前端测试, 返回成功, 测试删除功能后刷新网页, 一切正常. 点击进入商品详情页, 访问正常
第五步: 用户留言
1.models在最开始已经设置好了, 这里需要些LeavingMessageViewSet, 逻辑里面应该包含list, create, destroy功能, 要求和user_fav几乎一致, 例如, 需要登录认证, 只返回自己的留言信息(重写query_set)
serializer的model = UserLeavingMessage, 返回的字段除了留言的字段外, 应该注意user的取法serializers.CurrentUserDefault(), 以及必须返回id字段, 方便前端删除
代码如下:
class LeavingMessageSerializer(serializers.ModelSerializer): # 获取当前用户,并隐藏user字段 user = serializers.HiddenField( default=serializers.CurrentUserDefault() ) add_time = serializers.DateTimeField(read_only=True) # 知识点: read_only=True, 只返回, 不用提交 class Meta: model = UserLeavingMessage fields = ("user", "message_type", "subject", "message", "file", "id", "add_time") # 设置id字段是为了方便前端删除收藏
class LeavingMessageViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet): """ list: 获取用户留言 create: 添加留言 destroy: 删除留言 """ serializer_class = LeavingMessageSerializer # IsAuthenticated 用于验证用户是否登录 # IsOwnerOrReadOnly 用户只能删除自己的收藏信息 permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 可以通过 JWT的token认证登录, 也可以通过session登录 authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) # 只返回当前登录用户的留言 def get_queryset(self): return UserLeavingMessage.objects.filter(user=self.request.user)
# 用户留言接口(包含删除留言,新增留言) router.register(r'messages', LeavingMessageViewSet, basename="messages")
2. 先去添加一些留言信息
3. serializer知识点, 设置某一个返回的字段为read_only=True, 即只返回, 不用提交
4. serializer知识点, 设置serializers.DateTimeField的时间格式 add_time = serializers.DateTimeField(read_only=True, format="%Y-%m-%d %H:%M:%S")
5.Vue联调
6. 前端源码
7. 上传文件, 为何我们什么都没做, 但是上传文件如此简单, 这归因于 REST的 MultiPartParser 模块
第六步: 收货地址
1.有了前面的基础, 收货地址几乎可以直接copy过来. 新知识点: viewsets.ModelViewSet 包含了mixin的增删改查和GenericViewSet
2.由于前段的地址使用插件实现的: 省份+城市+区域, 所以后端为了方便, 这里我们创建三个字段来完成这个地址
class UserAddress(models.Model): province = models.CharField(max_length=100, default="", verbose_name="省份") city = models.CharField(max_length=100, default="", verbose_name="城市") district = models.CharField(max_length=100, default="", verbose_name="区域")
3. 代码如下,
viewset
# 收货地址, ModelViewSet包括了前面的mixin的增删改查所有功能 class AddressViewSet(viewsets.ModelViewSet): """ 收货地址管理 list: 获取收货地址 create: 添加收货地址 update: 修改收货地址 destroy: 删除收货地址 """ serializer_class = AddressSerializer permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication) def get_queryset(self): return UserAddress.objects.filter(user=self.request.user)
serializers, 需要自己去回忆局部钩子和validators属性
# 收货地址 class AddressSerializer(serializers.ModelSerializer): # 获取当前用户,并隐藏user字段 user = serializers.HiddenField( default=serializers.CurrentUserDefault() ) add_time = serializers.DateTimeField(read_only=True, format="%Y-%m-%d %H:%M:%S") signer_mobile = serializers.CharField(min_length=11, max_length=11, error_messages={ "min_length": "手机号码长度不对", "max_length": "手机号码长度不对", "required": "未收到手机号码字段", "blank": "请输入手机号码" }, # validators=[RegexValidator(REGEX_MOBILE, "手机号码非法")], ) # 用局部钩子来验证, 和上面单独加validator属性一样 def validate_signer_mobile(self, signer_mobile): if UserAddress.objects.filter(signer_mobile=signer_mobile): raise serializers.ValidationError("手机号码已存在") if not re.match(REGEX_MOBILE, signer_mobile): raise serializers.ValidationError("手机号码非法") return signer_mobile class Meta: model = UserAddress fields = ("id", "user", "province", "city", "district", "address", "signer_name", "add_time", "signer_mobile") # 设置id字段是为了方便前端删除收藏
urls
# 用户留言接口(包含删除留言,新增留言) router.register(r'address', AddressViewSet, basename="address")
4. 用doc文档功能来测试(主要看手机号的过滤功能)
5. 前端Vue联调
--- 君子处其实,不处其华;治其内,不治其外 张居正 ----