DRF实操学习——收货地址的设计
- 1.行政区划表的设计
- 2. 行政区划表接口演示
- 1.返回所有的省份
- 2. 查询指定上级行政区划的所有子区划,以及展示自身区划
- 3.行政区划表接口重写
- 补充:前端请求逻辑
- 4. 优化
- 5.收货地址的设计
- 6. 收货地址表接口重写
- 7.优化
- 1. 优化返回的数据
- 2.增加额外的校验,重写 validate_<field_name> 方法
分析:
- 提供收货地址的选择:提供行政区划的三级联动查询,省——市——区
- 完成用户保存添加的收货地址
1.行政区划表的设计
- 在users的models中增加模型类Area,然后迁移映射
# 行政区划表
class Area(models.Model):name = models.CharField(max_length=20,verbose_name='名称')# 'self'建立外键关联自身主键。添加的上级行政区划必须是行政区划表中已有的数据# 省份没有上级行政区划 on_delete=models.SET_NULL删除时设置为空,null=True允许为空# blank=True,parent = models.ForeignKey('self',on_delete=models.SET_NULL,null=True,blank=True,verbose_name='上级行政区划')class Meta:db_table = 'area'verbose_name = '行政区划'verbose_name_plural = verbose_name
- 将省份等行政区划的基础数据配置进去
- 编写序列化器
class AreaSerializer(ModelSerializer):class Meta:model = Areafields = ['id','name']
- 编写视图
class AreaViewSet(ModelViewSet):queryset = Area.objects.all()serializer_class = AreaSerializer
- 增加路由
from django.urls import path
from rest_framework.routers import DefaultRouter
from rest_framework_jwt.views import obtain_jwt_token
from .views import *urlpatterns = [path('login/', obtain_jwt_token),path('image/verification/<uuid:uuid>/', ImageVerifyView.as_view())
]
router = DefaultRouter()
router.register('users', UserViewSet)
router.register('area', AreaViewSet)
urlpatterns += router.urls
2. 行政区划表接口演示
分析:
把查询划分为两个接口
- 得到所有的省份:修改list视图函数,返回所有省份
- 把省份的id传入,修改详情路由,查询指定上级行政区划的所有子区划,以及展示自身区划
1.返回所有的省份
重写get_queryset方法,当操作为list操作时,查询所有parent=None的数据,即省份
class AreaViewSet(ModelViewSet):queryset = Area.objects.all()serializer_class = AreaSerializerdef get_queryset(self):if self.action == 'list':#查询所有parent=None的数据,即省份return Area.objects.filter(parent=None)else:return self.queryset
2. 查询指定上级行政区划的所有子区划,以及展示自身区划
- 优化返回结果,修改序列化器
# 行政区划序列化器
class AreaSerializer(ModelSerializer):"""行政区划自身序列化器"""class Meta:model = Areafields = ['id','name']# 增加一个新的行政区划的序列化器
class ParentSerializer(ModelSerializer):"""行政区划自身序列化器及子级区划"""# parent时外键关联自身,Area表时一对多查询# 生成管理器:源模型类名小写_set。通过管理器获取源模型类的所有数据area_set = AreaSerializer(many=True,read_only=True)class Meta:model = Areafields = ['id','name','area_set']
- 重写get_serializer_class
class AreaViewSet(ModelViewSet):queryset = Area.objects.all()serializer_class = AreaSerializerdef get_queryset(self):if self.action == 'list':# 查询所有parent=None的数据,即省份return Area.objects.filter(parent=None)else:return self.queryset# get_serializer返回的是对象,get_serializer_class返回的是类# 重写get_serializer或get_serializer_class都可以def get_serializer_class(self):if self.action == 'retrive':#省份return ParentSerializer else:return AreaSerializer
接口验证如下:
3.行政区划表接口重写
重写create、update、destroy方法
@wrap_permisssion(RootPermission)#修改权限为root用户def create(self, request, *args, **kwargs):return ModelViewSet.create(self, request, *args, **kwargs)@wrap_permisssion(RootPermission)#修改权限为root用户def update(self, request, *args, **kwargs):return ModelViewSet.create(self, request, *args, **kwargs)@wrap_permisssion(RootPermission)#修改权限为root用户def destroy(self, request, *args, **kwargs):return ModelViewSet.create(self, request, *args, **kwargs)
补充:前端请求逻辑
选择省份的输入框,会向后端发送请求,获取所有省份,渲染到前端。比如选择湖北省,然后前端获取到湖北省的id,再次向后端发起请求,获取到湖北省下的所有区划。同理,在根据孝感市的id获取到孝感市下的所有区划。
4. 优化
分析:
特点:增加、删除、修改、更新操作少。查询操作多。
所以可以增加缓存功能,因为msyql性能相对来收比较慢,主要是做持久化数据。redis相当于是内存,高速读取数据库。可以将查询操作多的数据放在redis中,即将高频数据或临时数据缓存。
-
下载第三方库drf-extensions
-
在settings中新增一个缓存配置
-
优化视图
from rest_framework_extensions.cache.mixins import CacheResponseMixin#继承CacheResponseMixin,那么该视图下的所有操作都会缓存到area库中
class AreaViewSet(CacheResponseMixin, ModelViewSet):queryset = Area.objects.all()serializer_class = AreaSerializer
- 发送查询请求后,查看redis的4号库,可以查看到数据,后续查询该数据时,会现在redis中查询数据是否存在,存在则直接返回,不存在,则去数据库中查询,然后再缓存在redis中
5.收货地址的设计
分析:
一个用户可以有多个收货地址:一对多关系。
定义收货地址模型类 ,新建一个收货地址表,外键关联用户表,保存是哪个用户创建的。
- 在users的models中增加模型类Address,然后迁移映射
# 行政区划表
class Address(ModelSetMixin):name = models.CharField(max_length=40,verbose_name='地址名')receiver = models.CharField(max_length=40,verbose_name='收货人')# on_delete=models.PROTECT受保护的,防止删除与该外键相关联的对象# 这里都关联了Area表,管理器都是area_set,管理器冲突,因此要重命名管理器名province = models.ForeignKey(Area,on_delete=models.PROTECT,verbose_name='省',related_name='province_address')city = models.ForeignKey(Area,on_delete=models.PROTECT,verbose_name='市',related_name='city_address')district = models.ForeignKey(Area,on_delete=models.PROTECT,verbose_name='区',related_name='district_address')place = models.CharField(max_length=40,verbose_name='详情地址')mobile = models.CharField(max_length=11,verbose_name='手机')user = models.ForeignKey(User,on_delete=models.CASCADE,verbose_name='用户')class Meta:ordering = ['-update_time']db_table = 'adress'verbose_name = '收货地址'verbose_name_plural = verbose_name
- 创建序列化器
class AddressSerializer(ModelSerializer):class Meta:model = Addressexclude = ['is_delete']
- 编写视图
分析:
创建:当前登录用户
修改:当前登录用户
删除:当前登录用户
查询:当前登录用户
查询详情:当前登录用户
所以我们可以对查询集进行处理
class AddressViewSet(ModelViewSet):queryset = Address.objects.all()serializer_class = AddressSerializerpermission_classes = [IsAuthenticated]def get_queryset(self):# 之前是判断有没有权限,可以返回数据# 这里直接只能返回该用户的数据,爬虫无法找到return self.request.user.address_set.filter(is_delete=False)
补充另一种写法:
不在视图中写queryset
则需要在路由中补充basename
- 增加路由
from django.urls import path
from rest_framework.routers import DefaultRouter
from rest_framework_jwt.views import obtain_jwt_token
from .views import *urlpatterns = [path('login/', obtain_jwt_token),path('image/verification/<uuid:uuid>/', ImageVerifyView.as_view())
]
router = DefaultRouter()
router.register('users', UserViewSet)
router.register('area', AreaViewSet)
# 如果在AddressViewSet不写queryset,则需要在路由中
# 增加basename,指定模型类
router.register('address', AddressViewSet, basename='address')
urlpatterns += router.urls
6. 收货地址表接口重写
@auto_userdef create(self, request, *args, **kwargs):max_count = 3if self.get_queryset().count() >= max_count:return Response({'detail':f'收货地址数量超过{max_count}条上限'},status=HTTP_400_BAD_REQUEST)return ModelViewSet.create(self, request, *args, **kwargs)@update_auto_userdef update(self, request, *args, **kwargs):return ModelViewSet.create(self, request, *args, **kwargs)@destory_auto_userdef destroy(self, request, *args, **kwargs):return ModelViewSet.create(self, request, *args, **kwargs)
7.优化
1. 优化返回的数据
修改序列化器
class AddressSerializer(ModelSerializer):province_name = serializers.CharField(source='province.name',read_only=True)city_name = serializers.CharField(source='city.name',read_only=True)district_name = serializers.CharField(source='district.name',read_only=True)class Meta:model = Addressexclude = ['is_delete']
查询结果如下:
2.增加额外的校验,重写 validate_<field_name> 方法
字段级别的校验
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import *
import reclass AddressSerializer(ModelSerializer):province_name = serializers.CharField(source='province.name',read_only=True)city_name = serializers.CharField(source='city.name',read_only=True)district_name = serializers.CharField(source='district.name',read_only=True)class Meta:model = Addressexclude = ['is_delete']#自定义校验器,增加额外的校验功能:重写 validate_<field_name> 方法,字段级别的校验# 对手机号码进行校验def validate_mobile(self,value):if not re.match(r'1[3-9]\d{9}$',value):raise serializers.ValidationError('手机号码格式错误')return value