• 参考网址
- https://blog.csdn.net/weixin_45905671/article/details/115052054
  • 页面展示

  • 数据响应如下
{
    "1":{ // 数字编号表示'组号',即对'一级目录'进行分组
        "channels":[ // 一级目录
            {"id":1, "name":"手机", "url":"http://shouji.jd.com/"},
            {"id":2, "name":"相机", "url":"http://www.baidu.cn/"}
        ],
        "sub_cats":[
            {
                "id":38, 
                "name":"手机通讯",  // 二级目录
                "sub_cats":[
                    {"id":115, "name":"手机"}, // 三级目录
                    {"id":116, "name":"游戏手机"}
                ]
            },
            {
                "id":39, 
                "name":"手机配件", // 二级目录
                "sub_cats":[
                    {"id":119, "name":"手机壳"},// 三级目录
                    {"id":120, "name":"贴膜"}
                ]
            }
        ]
    },
    "2":{
        "channels":[],
        "sub_cats":[]
    }
}

  • 需要两张表来支持以上数据,表模型如下
### models.py

class GoodsCategory(BaseModel):
    """
    商品类别(分组)
    """
    name = models.CharField(max_length=10, verbose_name='名称')
    parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, verbose_name='父类别')

    class Meta:
        db_table = 'tb_goods_category'
        verbose_name = '商品类别'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name


class GoodsChannel(BaseModel):
    """
    商品频道
    """
    group_id = models.IntegerField(verbose_name='组号')
    category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='顶级商品类别')
    url = models.CharField(max_length=50, verbose_name='频道页面链接')
    sequence = models.IntegerField(verbose_name='组内顺序')

    class Meta:
        db_table = 'tb_goods_channel'
        verbose_name = '商品频道'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.category.name
  • 视图逻辑如下: 要查询数据库,把数据手动拼接成上述结构返回给前端
### views

from django.shortcuts import render
from django.views import View
from goods.models import GoodsCategory, GoodsChannel
from collections import OrderedDict


class IndexView(View):
    """首页广告"""

    def get(self, request):
        """提供首页页面"""
        # 查询并展示商品分类

        # categories = {}  # 优化 OrderedDict 由无序变成有序
        categories = OrderedDict()
        # 查询所有的商品频道
        channels = GoodsChannel.objects.order_by('group_id', 'sequence')

        for channel in channels:
            # 37个频道 11个组
            group_id = channel.group_id
            # print(group_id)
            if group_id not in categories:
                categories[group_id] = {'channels': [], 'sub_cats': []}

            # print(categories)
            cat1 = channel.category
            categories[group_id]['channels'].append(
                {
                    "id": cat1.id,
                    "name": cat1.name,
                    "url": channel.url
                }
            )
            # print(categories)  # 打印结果一级分类完成
            # 查询二级和三级类别
            # 查询二级 parent_id = cat1.id
            # for cat2 in cat1.subs.all():  此行简写代码可替换下面一行代码  models.py中定义了related_name=subs
            for cat2 in GoodsCategory.objects.filter(parent_id=cat1.id).all():
                cat2.sub_cats = []
                categories[group_id]["sub_cats"].append(
                    {
                        "id": cat2.id,
                        "name": cat2.name,
                        "sub_cats": cat2.sub_cats
                    }
                )

                # for cat3 in cat2.subs.all():
                for cat3 in GoodsCategory.objects.filter(parent_id=cat2.id).all():
                    cat2.sub_cats.append(
                        {
                            "id": cat3.id,
                            "name": cat3.name,
                        }
                    )
        print(categories)

        '''
        {
            "1":{
                "channels":[
                    {"id":1, "name":"手机", "url":"http://shouji.jd.com/"},
                    {"id":2, "name":"相机", "url":"http://www.baidu.cn/"}
                ],
                "sub_cats":[
                    {
                        "id":38, 
                        "name":"手机通讯", 
                        "sub_cats":[
                            {"id":115, "name":"手机"},
                            {"id":116, "name":"游戏手机"}
                        ]
                    },
                    {
                        "id":39, 
                        "name":"手机配件", 
                        "sub_cats":[
                            {"id":119, "name":"手机壳"},
                            {"id":120, "name":"贴膜"}
                        ]
                    }
                ]
            },
        '''
        context = {
            "categories": categories
        }
        return render(request, 'index.html', context=context)


  • 前端页面
<ul class="sub_menu">
				{% for group in categories.values %}
				<li>
					<div class="level1">
						{% for channel in group.channels %}
						<a href="{{ channel.url }}">{{ channel.name }}</a>
						{% endfor %}
					</div>
					<div class="level2">
						{% for cat2 in group.sub_cats %}
						<div class="list_group">
							<div class="group_name fl">{{ cat2.name }} &gt;</div>
							<div class="group_detail fl">
								{% for cat3 in cat2.sub_cats %}
								<a href="/list/{{ cat3.id }}/1/">{{ cat3.name }}</a>
								{% endfor %}
							</div>
						</div>
						{% endfor %}
					</div>
				</li>
				{% endfor %}
</ul>

  • 问题,如果使用DRF框架来实现,数据结构如下,你要怎么写
{
    "1":{
        "channels":[
            {"id":1, "name":"手机", "url":"http://shouji.jd.com/"},
            {"id":2, "name":"相机", "url":"http://www.baidu.cn/"}
        ],
        "sub_cats":[
            {
                "id":38, 
                "name":"手机通讯", 
                "sub_cats":[
                    {"id":115, "name":"手机"},
                    {"id":116, "name":"游戏手机"}
                ]
            },
            {
                "id":39, 
                "name":"手机配件", 
                "sub_cats":[
                    {"id":119, "name":"手机壳"},
                    {"id":120, "name":"贴膜"}
                ]
            }
        ]
    },
    "2":{
        "channels":[],
        "sub_cats":[]
    }
}

- 思路分析: 分别序列化 GoodsCategory 和 GoodsChannel,再进行拼接

  • 利用ReadOnlyModelViewSet实现商品分类所有数据的查询 / 单个商品分类查询(即,查所有 / 查单个)
### goods.urls
from django.conf.urls import url
from rest_framework.routers import DefaultRouter

from . import views

urlpatterns = [
    ......
]

router = DefaultRouter() # 使用默认路由
router.register(r'goods_type',views.GoodsTypeViewSet,basename='goods_type')
urlpatterns += router.urls


### views
from rest_framework.viewsets import ReadOnlyModelViewSet

from .serilizers import GoodsCategoryModelSerilizer,GoodsCategoryDetailModelSerilizer
from .models import GoodsCategory

class GoodsTypeViewSet(ReadOnlyModelViewSet):
    queryset = GoodsCategory.objects.get_queryset()
    def get_serializer_class(self):
    	# 根据action选择不同的序列化器
    	# queryset就无须这样处理,因为DRF已经帮我们做了
        if self.action == 'list':
            return GoodsCategoryModelSerilizer
        else:
            return GoodsCategoryDetailModelSerilizer
            
### serializers
from rest_framework import serializers

from .models import GoodsCategory


# 查所有
class GoodsCategoryModelSerilizer(serializers.ModelSerializer):
    class Meta:
        model = GoodsCategory
        fields = ('id','name')
# 查单个
class GoodsCategoryDetailModelSerilizer(serializers.ModelSerializer):
    cats = serializers.SerializerMethodField()
    class Meta:
        model = GoodsCategory
        fields = ('id','name','cats')

    def get_cats(self,obj):
        subs = obj.subs.all()
        data = [] # 初始化最终返回的数据
        for sub in subs: # 获取第二层数据
            data_dict = {}
            data_dict['name'] = sub.name
            data_dict['id'] = sub.id
            data_dict['cats'] = []
            for sun in sub.subs.all(): # 获取第三层数据
                data_sun = {}
                data_sun['name'] = sun.name
                data_sun['id'] = sun.id
                data_dict['cats'].append(data_sun)
            data.append(data_dict)
        return data
  • 响应的数据如下
- url: http://127.0.0.1:8001/goods_type/ (查所有)

[
    {
        "id": 1,
        "name": "手机"
    },
    {
        "id": 2,
        "name": "相机"
    },
    {
        "id": 3,
        "name": "数码"
    },
    ......
    
- url: http://127.0.0.1:8001/goods_type/1/ (查单个)

{
    "id": 1,
    "name": "手机",
    "cats": [
        {
            "name": "手机通讯",
            "id": 38,
            "cats": [
                {
                    "name": "手机",
                    "id": 115
                },
                {
                    "name": "游戏手机",
                    "id": 116
                },
                ......
            ]
        },
        {
            "name": "手机配件",
            "id": 39,
            "cats": [
                {
                    "name": "手机壳",
                    "id": 119
                },
                {
                    "name": "贴膜",
                    "id": 120
                },
                ......
            ]
        }
    ]
}
  • 小结
- DRF 响应的数据格式中
	- 如果是查所有,返回的数据格式就是一个list里面,放N个dict,dict里面再放各种东东
	- 如果是查单个,返回的数据格式就是dict里面,放N个key和value存储各种东东

解决刚才存留的问题,我们在序列化器手动构造数据格式

### serilizers
......
class GoodsCategoryModelSerilizer(serializers.ModelSerializer):
    cats = serializers.SerializerMethodField()
    class Meta:
        model = GoodsCategory
        fields = ('id','name','cats')

    def get_cats(self,obj): # obj即每一个对象
        subs = obj.subs.all()
        data = []
        for sub in subs:
            data_dict = {}
            data_dict['name'] = sub.name
            data_dict['id'] = sub.id
            data_dict['cats'] = []
            for sun in sub.subs.all():
                data_sun = {}
                data_sun['name'] = sun.name
                data_sun['id'] = sun.id
                data_dict['cats'].append(data_sun)
                # data_dict['sun']['name'] = sun.name
                # data_dict['sun']['id'] = sun.id

            data.append(data_dict)
        return data
  • 响应数据如下:
- http://127.0.0.1:8001/goods_type/

[
    {
        "id": 1,
        "name": "手机",
        "cats": [
            {
                "name": "手机通讯",
                "id": 38,
                "cats": [
                    {
                        "name": "手机",
                        "id": 115
                    },
                    {
                        "name": "游戏手机",
                        "id": 116
                    },
                    {
                        "name": "老人机",
                        "id": 117
                    },
                    {
                        "name": "对讲机",
                        "id": 118
                    }
                ]
            },
            {
                "name": "手机配件",
                "id": 39,
                "cats": [
                    {
                        "name": "手机壳",
                        "id": 119
                    },
                    {
                        "name": "贴膜",
                        "id": 120
                    },
                    {
                        "name": "手机存储卡",
                        "id": 121
                    },
                    {
                        "name": "数据线",
                        "id": 122
                    },
                    {
                        "name": "充电器",
                        "id": 123
                    },
                    {
                        "name": "无线充电器",
                        "id": 124
                    },
                    {
                        "name": "手机耳机",
                        "id": 125
                    },
                    {
                        "name": "移动电源",
                        "id": 126
                    },
                    {
                        "name": "手机支架",
                        "id": 127
                    }
                ]
            }
        ]
    },
    {
        "id": 2,
        "name": "相机",
        "cats": [
            {
                "name": "摄影摄像",
                "id": 40,
                "cats": [
                    {
                        "name": "数码相机",
                        "id": 128
                    },
                    {
                        "name": "微单相机",
                        "id": 129
                    },
                    {
                        "name": "单反相机",
                        "id": 130
                    },
                    {
                        "name": "拍立得",
                        "id": 131
                    },
                    {
                        "name": "运动相机",
                        "id": 132
                    },
                    {
                        "name": "摄像机",
                        "id": 133
                    },
                    {
                        "name": "镜头",
                        "id": 134
                    },
                    {
                        "name": "数码相框",
                        "id": 135
                    }
                ]
            }
        ]
    },
    ......
]
  • 进一步简化前面所写的view,只留一个序列化器即可
    • 若不传pk,就查所有(包含子集)
    • 若传pk,就查单个(包含子集)
### views
......
class GoodsTypeViewSet(ReadOnlyModelViewSet):
 
    queryset = GoodsCategory.objects.get_queryset()
    serializer_class = GoodsCategoryModelSerilizer

    # def get_serializer_class(self):
    #     if self.action == 'list':
    #         return GoodsCategoryModelSerilizer
    #     else:
    #         return GoodsCategoryDetailModelSerilizer
  • 小结:对于查单个或者查所有这种接口,可以使用ReadOnlyModelViewSet来搞定
- 写法上,指定所有的 queryset 和 serializer_class 即可

	class GoodsTypeViewSet(ReadOnlyModelViewSet):
        queryset = GoodsCategory.objects.get_queryset()
        serializer_class = GoodsCategoryModelSerilizer

- 子集的'cats'字段需要自己手动构造数据来实现(类似三级联动这种数据,嵌套两层循环即可)
	
	class GoodsCategoryModelSerilizer(serializers.ModelSerializer):
   
        cats = serializers.SerializerMethodField()
        class Meta:
            model = GoodsCategory
            fields = ('id','name','cats')

        def get_cats(self,obj):
            subs = obj.subs.all()
            data = []
            for sub in subs:
                data_dict = {}
                data_dict['name'] = sub.name
                data_dict['id'] = sub.id
                data_dict['cats'] = []
                for sun in sub.subs.all():
                    data_sun = {}
                    data_sun['name'] = sun.name
                    data_sun['id'] = sun.id
                    data_dict['cats'].append(data_sun)
                    # data_dict['sun']['name'] = sun.name
                    # data_dict['sun']['id'] = sun.id

                data.append(data_dict)
            return data

商品频道的三级联动数据展示

  • 上述逻辑搞定以后,本次接口就显得很简单了
### urls
......

urlpatterns = [
    ......
]

router = DefaultRouter()
router.register(r'goods_type',views.GoodsTypeViewSet,basename='goods_type')
# 增加接口
router.register(r'channel_goods',views.ChannelGoodsViewSet,basename='channel_goods')
urlpatterns += router.urls

### views
......
class ChannelGoodsViewSet(ModelViewSet):
    '''
        - ModelViewSet提供了 增删改(更新所有/更新局部)查(查所有/查单个) 全套服务
    '''
    # 排序
    queryset = GoodsChannel.objects.get_queryset().order_by('group_id')
    serializer_class = ChannelGoodsModelSerilizer
   
### serializers
......
class ChannelGoodsModelSerilizer(serializers.ModelSerializer):
    category = GoodsCategoryModelSerilizer() # 引用之前的序列化器
    class Meta:
        model = GoodsChannel
        exclude = ['sequence']
  • 测试结果
- 查所有: http://127.0.0.1:8001/channel_goods/

	[
        {
            "id": 1,
            "category": {
                "id": 1,
                "name": "手机",
                "cats": [
                    {
                        "name": "手机通讯",
                        "id": 38,
                        "cats": [
                            {
                                "name": "手机",
                                "id": 115
                            },
                            ......
                            {
                                "name": "对讲机",
                                "id": 118
                            }
                        ]
                    },
                    {
                        "name": "手机配件",
                        "id": 39,
                        "cats": [
                            {
                                "name": "手机壳",
                                "id": 119
                            },
                            ......
                            {
                                "name": "手机支架",
                                "id": 127
                            }
                        ]
                    }
                ]
            },
            "create_time": "2018-04-09T09:15:38.057078Z",
            "update_time": "2018-04-09T09:15:38.057150Z",
            "group_id": 1,
            "url": "http://shouji.jd.com"
        },
        {......}
	]
	
- 查单个: http://127.0.0.1:8001/channel_goods/1/
	
	{
        "id": 1,
        "category": {
            "id": 1,
            "name": "手机",
            "cats": [
                {
                    "name": "手机通讯",
                    "id": 38,
                    "cats": [
                        {
                            "name": "手机",
                            "id": 115
                        },
                       ......
                        {
                            "name": "对讲机",
                            "id": 118
                        }
                    ]
                },
                {
                    "name": "手机配件",
                    "id": 39,
                    "cats": [
                        {
                            "name": "手机壳",
                            "id": 119
                        },
                        ......
                        {
                            "name": "手机支架",
                            "id": 127
                        }
                    ]
                }
            ]
        },
        "create_time": "2018-04-09T09:15:38.057078Z",
        "update_time": "2018-04-09T09:15:38.057150Z",
        "group_id": 1,
        "url": "http://shouji.jd.com"
    }