DRF实操学习——商城
- 1. 商城模型的设计
- 2. 优化
- 商品分类表
- 1. 序列化器和查询集的优化
- 2. 得到指定类目的所有商品
- 商品表
- 1. 视图的基础权限等配置
- 2. 增加上传商品图片的接口
- 3. 优选商品接口
分析:
- 商品分类表
- 商品表
- 商品图片表:一个商品有多张图片
1. 商城模型的设计
-
新建一个shopping的app
-
在settings中注册该app
-
创建模型,迁移映射
from django.db import models
from utils.modelsMixin import ModelSetMixin
# 商品分类表
class Classification(ModelSetMixin):
name = models.CharField(max_length=40,verbose_name='分类名')
# models.SET_NULL如果上级分类被删了,设置为空。外键关联自身
parent = models.ForeignKey('self',on_delete=models.SET_NULL,null=True,blank=True,verbose_name='上级分类')
class Meta:
db_table = 'classification'
verbose_name = '商品分类'
verbose_name_plural = verbose_name
def __str__(self):
return f'%s.%s' % (self.id,self.name)
# 商品表
class Commodity(ModelSetMixin):
STATUS_CHOICES = (
(0,'未发布'),
(1,'发布'),
)
name = models.CharField(max_length=40,verbose_name='商品名')
caption = models.CharField(max_length=40,verbose_name='副标题')
brand = models.CharField(max_length=40,verbose_name='品牌',null=True,blank=True)
# 注:FloatField有计算精度的问题,如果有小数且参与计算,用DecimalField
# 如果有小数,但是不怎么参与计算,那么可以使用FloatField
# max_digits最大的位数 decimal_places小数为的最大位数
price = models.DecimalField(max_digits=10,decimal_places=2,verbose_name='单价')
stock = models.IntergerField(verbose_name='库存')
pack = models.TextField(verbose_name='包装信息',null=True,blank=True)
serviceafter_sale = models.TextField(verbose_name='售后服务',null=True,blank=True)
sales = models.IntergerField(verbose_name='销量',default=0)
comments = models.IntergerField(verbose_name='评价数',default=0)
status = models.IntergerField(verbose_name='状态',default=0,choices=STATUS_CHOICES)
detail = models.TextField(verbose_name='详情',null=True,blank=True)
classification1 = models.ForeignKey(Classification,on_delete=models.PROTECT,verbose_name='一级分类',related_name='一级分类')
classification2 = models.ForeignKey(Classification,on_delete=models.PROTECT,verbose_name='二级分类',related_name='二级分类')
class Meta:
ordering=['-sales','-comments','-create_time']
db_table = 'commodity'
verbose_name = '商品'
verbose_name_plural = verbose_name
# 商品图片表
class CommodityImg(models.Model):
src = models.TextField(verbose_name='图片地址')
priority = models.IntegerField(default=0,verbose_name='优先级')
commodity = models.ForeignKey(Commodity,on_delete=models.CASCADE,verbose_name='商品')
class Meta:
ordering=['-priority','-id']
db_table = 'commodity_img'
verbose_name = '商品图'
verbose_name_plural = verbose_name
- 创建序列化器
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import *
class ClassificationSerializer(ModelSerializer):
class Meta:
model = Classification
exclude = ['is_delete']
class CommoditySerializer(ModelSerializer):
class Meta:
model = Commodity
exclude = ['is_delete']
class CommodityImgSerializer(ModelSerializer):
class Meta:
model = CommodityImg
fields = '__all__'
- 创建视图
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from .serializers import *
class ClassificationViewSet(ModelViewSet):
queryset = Classification.objects.filter(is_delete=False)
serializer_class = ClassificationSerializer
class CommodityViewSet(ModelViewSet):
queryset = Commodity.objects.filter(is_delete=False)
serializer_class = CommoditySerializer
class CommodityImgViewSet(ModelViewSet):
queryset = CommodityImg.objects.filter(is_delete=False)
serializer_class = CommodityImgSerializer
- 创建路由
from django.urls import path
from rest_framework.routers import DefaultRouter
from rest_framework_jwt.views import obtain_jwt_token
from .views import *
urlpatterns = []
router = DefaultRouter()
router.register('classifications', ClassificationViewSet)
router.register('commodity', CommodityViewSet)
router.register('commodity_img', CommodityImgViewSet)
urlpatterns += router.urls
- 增加主路由
from django.contrib import admin
from django.urls import path, include
from rest_framework.documentation import include_docs_urls
urlpatterns = [
path('admin/', admin.site.urls),
path('drfstudy/', include('drfstudy.urls')),
path('users/', include('users.urls')),
path('school/', include('school.urls')),
path('work/', include('work.urls')),
path('community/', include('community.urls')),
path('shopping/', include('shopping.urls')),
path('docs/', include_docs_urls('DRF6项目接口文档')) # 配置接口文档路由地址,文档标题
]
2. 优化
商品分类表
1. 序列化器和查询集的优化
- 序列化器的优化
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import *
class ClassificationSerializer(ModelSerializer):
class Meta:
model = Classification
exclude = ['is_delete']
class ParentClassificationSerializer(ModelSerializer):
# 要把子集展示出来,使用管理器。源模型小写_set
classification_set = ClassificationSerializer(many=True,read_only=True)
class Meta:
model = Classification
exclude = ['is_delete']
- 查询集的优化
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from .serializers import *
class ClassificationViewSet(ModelViewSet):
queryset = Classification.objects.filter(is_delete=False,parent=None)
serializer_class = ParentClassificationSerializer
- 查询结果如下
- 优化查询结果,继续修改序列化器
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import *
class ClassificationSerializer(ModelSerializer):
class Meta:
model = Classification
fields = ['id','name']
class ParentClassificationSerializer(ModelSerializer):
# 要把子集展示出来,使用管理器。源模型小写_set
classification_set = ClassificationSerializer(many=True,read_only=True)
class Meta:
model = Classification
fields = ['id','name','classification_set','parent']
# 更改属性
extra_kwargs = {
# 将parent字段设置为只写,无法查看
'parent':{'write_only':True}
}
优化后查询结果如下:
- 增加权限
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from .serializers import *
from rest_framework.permissions import IsAuthenticated
class ClassificationViewSet(ModelViewSet):
queryset = Classification.objects.filter(is_delete=False,parent=None)
serializer_class = ParentClassificationSerializer
permission_classes = [IsAuthenticated]
#更改权限装饰器,只有老师才有权限创建
@wrap_permisssion(TeacherPermission)
def create(self, request, *args, **kwargs):
return ModelViewSet.create(self, request, *args, **kwargs)
@wrap_permisssion(TeacherPermission)
def update(self, request, *args, **kwargs):
return ModelViewSet.update(self, request, *args, **kwargs)
@wrap_permisssion(TeacherPermission)
def destroy(self, request, *args, **kwargs):
return ModelViewSet.destroy(self, request, *args, **kwargs)
2. 得到指定类目的所有商品
在ClassificationViewSet中增加以下接口
@action(methods=['get'],detail=True)
def commodity(self,request,pk):
try:
classification = Classification.objects.filter(is_delete=False).get(id=pk)
except Classification.DoesNotExist:
return response(status=HTTP_404_NOT_FOUND)
# 得到这个类目的所有商品,使用管理器
# 判断类目是顶层类目还是子类目
# 默认为顶层类目
classification_attr = 'classification1'
# 顶层类目 展示自身类目名
classification_name = classification.name
if classification.parent:
classification_attr = 'classification2'
classification_name = f'{classification.parent.name}---{classification.name}'
# 使用内置函数getattr获取属性
data = getattr(classification,classification_attr).filter(is_delete=False,status=1)
return Response({
'classification':classification_name,
'commodity':CommoditySerializer(data,many=True).data
})
商品表
1. 视图的基础权限等配置
class CommodityViewSet(ModelViewSet):
queryset = Commodity.objects.filter(is_delete=False)
serializer_class = CommoditySerializer
permission_classes = [IsAuthenticated]
# 重写get_queryset方法的场景:根据不同的操作场景设置不同的查询集
def get_queryset(self):
if self.action in ['list','retrieve']: #如果是查询,则只能返回已发布的商品
return Commodity.objects.filter(is_delete=False,status=1)
return self.queryset
#更改权限装饰器,只有老师才有权限创建
@wrap_permisssion(TeacherPermission)
def create(self, request, *args, **kwargs):
return ModelViewSet.create(self, request, *args, **kwargs)
@wrap_permisssion(TeacherPermission)
def update(self, request, *args, **kwargs):
return ModelViewSet.update(self, request, *args, **kwargs)
@wrap_permisssion(TeacherPermission)
def destroy(self, request, *args, **kwargs):
return ModelViewSet.destroy(self, request, *args, **kwargs)
2. 增加上传商品图片的接口
from config.fastdfsConfig import FASTDFS_SERVER_DOMAIN
from fdfs_client.client import Fdfs_client, get_tracker_conf
import logging
# 导入日志器
logger = logging.getLogger(__name__)
# 创建FastDFS客户端对象
tracker_path = get_tracker_conf('utils/fastdfs/client.conf')
client = Fdfs_client(tracker_path)
class CommodityViewSet(ModelViewSet):
queryset = Commodity.objects.filter(is_delete=False)
serializer_class = CommoditySerializer
permission_classes = [IsAuthenticated]
# 重写get_queryset方法的场景:根据不同的操作场景设置不同的查询集
def get_queryset(self):
if self.action in ['list','retrieve']: #如果是查询,则只能返回已发布的商品
return Commodity.objects.filter(is_delete=False,status=1)
return self.queryset
#更改权限装饰器,只有老师才有权限创建
@wrap_permisssion(TeacherPermission)
def create(self, request, *args, **kwargs):
return ModelViewSet.create(self, request, *args, **kwargs)
@wrap_permisssion(TeacherPermission)
def update(self, request, *args, **kwargs):
return ModelViewSet.update(self, request, *args, **kwargs)
@wrap_permisssion(TeacherPermission)
def destroy(self, request, *args, **kwargs):
return ModelViewSet.destroy(self, request, *args, **kwargs)
@action(methods=['post'],detail=True)
def img(self,request,pk):
"""
上传图片
"""
#获取商品
try:
commodity = self.get_queryset().get(id=pk)
except Commodity.DoesNotExist:
return Response(status=HTTP_404_NOT_FOUND)
# 上传图片
# 得到上传的文件数据
file = request.FILES.get('file')
# 判断是否有图片数据,文件类型是否是图片
if not file or file.content_type not in ('image/jpeg', 'image/png', 'image/gif'):
# 不是,返回客户端传入数据错误
return Response(status=HTTP_400_BAD_REQUEST)
# 是,交给fastdfs,存储到可用的storage服务上
# 得到文件后缀名,如果没有后缀名则默认为png类型
try:
images_ext_name = file.name.split('.')[-1]
except Exception as e:
logger.info('图片拓展名异常:%s' % e)
images_ext_name = 'png'
# 上传图片数据,通过字节流
try:
upload_res = client.upload_by_buffer(file.read(), file_ext_name=images_ext_name)
except Exception as e:
logger.error('图片上传出现异常:%s' % e)
return Response(status=HTTP_500_INTERNAL_SERVER_ERROR)
else:
if upload_res.get('Status') != 'Upload successed.':
logger.warning('图片上传失败')
return Response(status=HTTP_500_INTERNAL_SERVER_ERROR)
# 得到返回的file_id
image_name = upload_res.get('Remote file_id').decode()
image_url = FASTDFS_SERVER_DOMAIN + image_name
#保存图片地址
CommodityImg.objects.create(src=image_url,commodity = commodity)
#返回
return Response({'data':image_url})
修改序列化器,优化查询结果
class CommodityImgSerializer(ModelSerializer):
class Meta:
model = CommodityImg
fields = ['src']
class CommoditySerializer(ModelSerializer):
# classification1是外键关联的Classification表
classification1_name = serializers.CharField(source='classification1.name',read_only=True)
classification2_name = serializers.CharField(source='classification2.name',read_only=True)
# 添加管理器:类名小写_set
commodityimg_set = CommodityImgSerializer(many=True,read_only=True)
class Meta:
model = Commodity
exclude = ['is_delete']
优化后的查询结果如下:
3. 优选商品接口
在CommodityViewSet视图中增加以下接口
@action(methods=['get'],detail=False)
def optimization(self,request):
"""
优选商品接口,返回评论数最多的前五个商品
"""
# 这里使用order_by会覆盖在模型中使用的order_by
queryset = self.get_queryset().order_by('-comments').order_by('-create_time')
# 使用切片返回前五条数据:0,1,2,3,4
return Response(self.get_serializer(queryset[:5],many=True).data)