DRF实操学习——收货地址的设计
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 = Area
fields = ['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 = AreaSerializer
def 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 = Area
fields = ['id','name']
# 增加一个新的行政区划的序列化器
class ParentSerializer(ModelSerializer):
"""
行政区划自身序列化器及子级区划
"""
# parent时外键关联自身,Area表时一对多查询
# 生成管理器:源模型类名小写_set。通过管理器获取源模型类的所有数据
area_set = AreaSerializer(many=True,read_only=True)
class Meta:
model = Area
fields = ['id','name','area_set']
- 重写get_serializer_class
class AreaViewSet(ModelViewSet):
queryset = Area.objects.all()
serializer_class = AreaSerializer
def 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 = Address
exclude = ['is_delete']
- 编写视图
分析:
创建:当前登录用户
修改:当前登录用户
删除:当前登录用户
查询:当前登录用户
查询详情:当前登录用户
所以我们可以对查询集进行处理
class AddressViewSet(ModelViewSet):
queryset = Address.objects.all()
serializer_class = AddressSerializer
permission_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_user
def create(self, request, *args, **kwargs):
max_count = 3
if 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_user
def update(self, request, *args, **kwargs):
return ModelViewSet.create(self, request, *args, **kwargs)
@destory_auto_user
def 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 = Address
exclude = ['is_delete']
查询结果如下:
2.增加额外的校验,重写 validate_<field_name> 方法
字段级别的校验
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import *
import re
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 = Address
exclude = ['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