保存SKU表数据
在保存数据之前我们需要先获取三级分类信息、SPU表的名称信息、当前SPU商品的规格选项信息加载到页面中
接口分析
请求方式: GET /meiduo_admin/skus/categories/
# -------获取三级分类信息--------
url(r'skus/categories/$', skus.SKUCategorieView.as_view()),
【图片中重写的方法名,作用相同于下面写的类方法】
请求参数: 通过请求头传递jwt token数据。
返回数据: JSON
[
{
"id": "商品分类id",
"name": "商品分类名称"
},
...
]
返回值类型是否必须说明Idint是商品分类idname数组是商品分类名称
后端实现
from rest_framework.viewsets import ModelViewSet
from meiduo_admin.serializers.skus import SKUGoodsSerializer
from meiduo_admin.utils import UserPageNum
from goods.models import SKU
from rest_framework.generics import ListAPIView
from meiduo_admin.serializers.skus import SKUCategorieSerializer
from goods.models import GoodsCategory
class SKUCategorieView(ListAPIView):
serializer_class = SKUCategorieSerializer
# 根据数据存储规律parent_id大于37为三级分类信息,查询条件为parent_id__gt=37
queryset = GoodsCategory.objects.filter(parent_id__gt=37)
序列化器的定义
from rest_framework import serializers
from goods.models import SKU,GoodsCategory
from goods.models import SKUSpecification
from goods.models import GoodsCategory
class SKUCategorieSerializer(serializers.ModelSerializer):
"""
商品分类序列化器
"""
class Meta:
model = GoodsCategory
fields = "__all__"
2、获取spu表名称数据
接口分析
请求方式: GET /meiduo_admin/goods/simple/
# -------获取spu表名称数据--------
url(r'goods/simple/$', skus.SPUSimpleView.as_view()), # 此行代码和上面规格路由表的作用是一样的
请求参数: 通过请求头传递jwt token数据。
返回数据: JSON
[
{
"id": "商品SPU ID",
"name": "SPU名称"
},
...
]
返回值类型是否必须说明Idint是商品SPU IDname数组是SPU名称
后端实现
class SPUSimpleView(ListAPIView):
serializer_class = SPUSimpleSerializer
queryset = SPU.objects.all()
定义序列化器
class SPUSimpleSerializer(serializers.ModelSerializer):
"""
商品SPU表序列化器
"""
class Meta:
model = GoodsCategory
fields = ('id', 'name')
3、获取SPU商品规格信息
接口分析
请求方式: GET meiduo_admin/goods/(?P\d+)/specs/
# -------获取spu商品规格信息--------
url(r'goods/(?P\d+)/specs/$', skus.SPUSpecView.as_view()),
请求参数: 通过请求头传递jwt token数据。
在路径中传递当前SPU商品id
返回数据: JSON
[
{
"id": "规格id",
"name": "规格名称",
"spu": "SPU商品名称",
"spu_id": "SPU商品id",
"options": [
{
"id": "选项id",
"name": "选项名称"
},
...
]
},
...
]
返回值类型是否必须说明Idint是规格idnameStr是规格名称Supstr是Spu商品名称Spu_idInt是spu商品idoptions是关联的规格选项
后端实现
class SPUSpecView(ListAPIView):
serializer_class = SPUSpecSerialzier
# 因为我们继承的是ListAPIView,在拓展类中是通过get_queryset获取数据,但是我们现在要获取的是规格信息,所以重写get_queryset
def get_queryset(self):
# 获取spuid值
pk=self.kwargs['pk']
# 根据spu的id值关联过滤查询出规格信息
return SPUSpecification.objects.filter(spu_id=self.kwargs['pk'])
定义序列化器
from goods.models import GoodsCategory, SpecificationOption, SPUSpecification
class SPUOptineSerializer(serializers.ModelSerializer):
"""
规格选项序列化器
"""
class Meta:
model = SpecificationOption
fields = ('id', 'value')
class SPUSpecSerialzier(serializers.ModelSerializer):
"""
规格序列化器
"""
# 关联序列化返回SPU表数据
spu = serializers.StringRelatedField(read_only=True)
spu_id = serializers.IntegerField(read_only=True)
# 关联序列化返回 规格选项信息
options = SPUOptineSerializer(read_only=True, many=True) # 使用规格选项序列化器
class Meta:
model = SPUSpecification # SPUSpecification中的外键spu关联了SPU商品表
fields = "__all__"
4、保存SKU数据
【保存数据时若不显示,则刷新一下】
接口分析
请求方式: POST meiduo_admin/skus/
# sku表查询路由****************************
router = DefaultRouter()
router.register('skus', skus.SKUGoodsView, base_name='sku')
urlpatterns += router.urls
请求参数: 通过请求头传递jwt token数据。
参数类型是否必须说明namestr是商品SKU名称spu_idint是商品SPU IDcaptionstr是商品副标题category_idint是三级分类IDpriceint是价格cost_priceint是进价market_priceint是市场价stockint是库存is_launchedboole是上下架返回数据: JSON
{
"id": "商品SKU ID",
"name": "商品SKU名称",
"goods": "商品SPU名称",
"goods_id": "商品SPU ID",
"caption": "商品副标题",
"category_id": "三级分类id",
"category": "三级分类名称",
"price": "价格",
"cost_price": "进价",
"market_price": "市场价",
"stock": "库存",
"sales": "销量",
"is_launched": "上下架",
"specs": [
{
"spec_id": "规格id",
"option_id": "选项id"
},
...
]
}
参数类型是否必须说明namestr是商品SKU名称spu_idint商品SPU IDcaptionstr商品副标题category_idint三级分类IDpriceint价格cost_priceint进价market_priceint市场价stockint库存is_launchedboole上下架
后端实现:
在后端实现中,我们需要异步生成详情页静态页面,同时涉及到多张表操作,我们还需要使用事务
# SKUGoodsView继承的是ModelViewSet 所以保存逻辑还是使用同一个类视图
class SKUGoodsView(ModelViewSet):
serializer_class =SKUGoodsSerializer
pagination_class = PageNum
def get_queryset(self):
keyword=self.request.query_params.get('keyword')
if keyword == '' or keyword is None:
return SKU.objects.all()
else:
return SKU.objects.filter(name=keyword)
序列化器的定义
from django.db import transaction
from celery_tasks.static_file.tasks import get_detail_html
class SKUSerializer(serializers.ModelSerializer):
"""
SKU表数据
"""
# 返回关联spu表的名称和关联的分类表的名称
spu = serializers.StringRelatedField(read_only=True)
category = serializers.StringRelatedField(read_only=True)
# 返回模型类类的spu_id和category_id
spu_id = serializers.IntegerField()
category_id = serializers.IntegerField()
# 返回商品的规格信息 ,在商品规格详情表(SKUSpecification)中有个外键sku关了当前的SKU表
specs = SKUSpecificationSerializer(many=True)
class Meta:
model = SKU
fields = "__all__"
def create(self, validated_data):
# self指的是当前序列化器对象,在self下面有个context属性保存了请求对象
specs=self.context['request'].data.get('specs')
# specs = validated_data['specs']
# 因为sku表中没有specs字段,所以在保存的时候需要删除validated_data中specs数据
del validated_data['specs']
with transaction.atomic():
# 开启事务
sid = transaction.savepoint()
try:
# 1、保存sku表
sku = SKU.objects.create(**validated_data)
# 2、保存SKU具体规格
for spec in specs:
SKUSpecification.objects.create(sku=sku, spec_id=spec['spec_id'], option_id=spec['option_id'])
except:
# 捕获异常,说明数据库操作失败,进行回滚
transaction.savepoint_rollback(sid)
return serializers.ValidationError('数据库错误')
else:
# 没有捕获异常,数据库操作成功,进行提交
transaction.savepoint_commit(sid)
# 执行异步任务生成新的静态页面
get_detail_html.delay(sku.id)
return sku
异步任务:
import os
from django.conf import settings
from django.shortcuts import render
from goods.models import SKU
from meiduo_mall.utils.breadcrumb import get_breadcrumb
from meiduo_mall.utils.categories import get_categories
from celery_tasks.main import app
@app.task(name='get_detail_html')
def get_detail_html(sku_id):
# 获取当前sku对象
sku=SKU.objects.get(id=sku_id)
# 分类数据
categories = get_categories()
# 获取面包屑导航
breadcrumb = get_breadcrumb(sku.category)
# 获取spu
spu = sku.spu
# 获取规格信息:sku===>spu==>specs
specs = spu.specs.order_by('id')
# 查询所有的sku,如华为P10的所有库存商品
skus = spu.skus.order_by('id')
'''
{
选项:sku_id
}
说明:键的元组中,规格的索引是固定的
示例数据如下:
{
(1,3):1,
(2,3):2,
(1,4):3,
(2,4):4
}
'''
sku_options = {}
sku_option = []
for sku1 in skus:
infos = sku1.specs.order_by('spec_id')
option_key = []
for info in infos:
option_key.append(info.option_id)
# 获取当前商品的规格信息
if sku.id == sku1.id:
sku_option.append(info.option_id)
sku_options[tuple(option_key)] = sku1.id
# 遍历当前spu所有的规格
specs_list = []
for index, spec in enumerate(specs):
option_list = []
for option in spec.options.all():
# 如果当前商品为蓝、64,则列表为[2,3]
sku_option_temp = sku_option[:]
# 替换对应索引的元素:规格的索引是固定的[1,3]
sku_option_temp[index] = option.id
# 为选项添加sku_id属性,用于在html中输出链接
option.sku_id = sku_options.get(tuple(sku_option_temp), 0)
# 添加选项对象
option_list.append(option)
# 为规格对象添加选项列表
spec.option_list = option_list
# 重新构造规格数据
specs_list.append(spec)
context = {
'sku': sku,
'categories': categories,
'breadcrumb': breadcrumb,
'category_id': sku.category_id,
'spu': spu,
'specs': specs_list
}
response = render(None, 'detail.html', context)
file_name = os.path.join(settings.BASE_DIR, 'static/detail/%d.html' % sku.id)
# 写文件
with open(file_name, 'w') as f1:
f1.write(response.content.decode())