您现在的位置是: 网站首页 >Django >Vue+Django REST framework前后端分离生鲜电商 Django

【Vue+DRF生鲜电商】10.商品分类层级获取,Vue跨域请求商品分类

admin2019年4月26日 17:41 Django | Python | Vue 2198人已围观

Vue+Django REST framework前后端分离生鲜电商简介 Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。 Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket ; Django版本:2.2、djangorestframework:3.9.2。 前端Vue模板可以直接联系我拿。

## DRF实现商品分类获取 实现商品分类层级结构显示 ### 商品类别ViewSet 在商品类别中,不需要对类别进行分页,因为类别的数据量不大,只要在数据量很大的时候才能分页,所以前面需要去掉全局分页配置,否则会影响这儿的结果。 修改 views.py 增加分类的视图 ```python class CategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): # 注释很有用,在drf文档中,可以自动提取出来 """ list: 商品分类列表 """ queryset = GoodsCategory.objects.all() # 取出所有分类,没必要分页,因为分类数据量不大 serializer_class = CategorySerializer # 使用商品类别序列化类,写商品的分类外键已有,直接调用 ``` 该序列化类可以直接使用之前商品列表分类外键已经创建好的,不用再新建,下方是已有的代码内容 ```python class CategorySerializer(serializers.ModelSerializer): class Meta: model = GoodsCategory fields = '__all__' ``` ### 商品类别URL配置 修改主 urls.py ,在router中注册分类的url ```python from rest_framework.routers import DefaultRouter from goods.views import GoodsListView, GoodsListViewSet, CategoryViewSet # 创建一个路由器并注册我们的视图集 router = DefaultRouter() router.register(r'goods', GoodsListViewSet, base_name='goods') # 配置goods的url router.register(r'categories', CategoryViewSet, base_name='categories') # 配置分类的url urlpatterns = [ path('admin/', admin.site.urls), path('api-auth/', include('rest_framework.urls')), # drf 认证url path('ckeditor/', include('ckeditor_uploader.urls')), # 配置富文本编辑器url path('', include(router.urls)), # API url现在由路由器自动确定。 # DRF文档 path('docs/', include_docs_urls(title='DRF文档')), ] ``` 这时访问 http://127.0.0.1:8000/categories/ 即可查看到所有的分类列表 ![BLOG_20190426_174504_46](/media/blog/images/2019/04/BLOG_20190426_174504_46.png "博客图集BLOG_20190426_174504_46.png") 但是,这些分类数据都是一起显示出来的,没有层次结构。 ### 层次化商品类别 首先访问 http://127.0.0.1:8000/goods/ 记录下每一种商品的分类外键数据,因为之后要修改分类的序列化类,可以做下对比 ![BLOG_20190426_174456_40](/media/blog/images/2019/04/BLOG_20190426_174456_40.png "博客图集BLOG_20190426_174456_40.png") 分析:在前端页面上,拿分类数据时,首先是拿到一类分类,二级分类是在一级分类下面的,三级分类是二级分类下面的,可以看出,分类数据是有个层次结构的。 ![BLOG_20190426_174438_12](/media/blog/images/2019/04/BLOG_20190426_174438_12.png "博客图集BLOG_20190426_174438_12.png") #### ViewSet只获取一级分类 既然这样,我们就可以**在视图中指定只获取一级分类数据**。 修改 views.py ```python class CategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): # 注释很有用,在drf文档中 """ list: 商品分类列表 """ # queryset = GoodsCategory.objects.all() # 取出所有分类,没必要分页,因为分类数据量不大 queryset = GoodsCategory.objects.filter(category_type=1) # 只获取一级分类数据 serializer_class = CategorySerializer # 使用商品类别序列化类,写商品的分类外键已有,直接调用 ``` 访问 http://127.0.0.1:8000/categories/ 即显示一级分类数据 ![BLOG_20190426_174426_69](/media/blog/images/2019/04/BLOG_20190426_174426_69.png "博客图集BLOG_20190426_174426_69.png") 那怎么才能通过一级分类获取到二级分类数据呢?之前写商品列表需要显示分类外键内容时,通过覆写`category`自定义序列化类实现。 #### 序列化二级分类 在分类模型中,有个`parent_category = models.ForeignKey('self', null=True, blank=True, verbose_name='父级目录', help_text='父级目录', on_delete=models.CASCADE, related_name='sub_category')`字段,也就是可以通过`sub_category`字段获取子类对象。序列化类的字段也需要和这个字段保持一样。 修改 serializers.py ```python class CategorySerializer2(serializers.ModelSerializer): class Meta: model = GoodsCategory fields = '__all__' class CategorySerializer(serializers.ModelSerializer): sub_category = CategorySerializer2(many=True) # 通过一级分类获取到二级分类,由于一级分类下有多个二级分类,需要设置many=True class Meta: model = GoodsCategory fields = '__all__' ``` 这就实现了一级分类下显示二级分类,访问 http://127.0.0.1:8000/categories/?format=json 即可看到,使用json方便收起 ![BLOG_20190426_174404_57](/media/blog/images/2019/04/BLOG_20190426_174404_57.png "博客图集BLOG_20190426_174404_57.png") #### 序列化三级分类 三级分类也是使用一样的办法,因为分类数据的格式都是一样的 修改 serializers.py ,增加三级分类序列化 ```python class CategorySerializer3(serializers.ModelSerializer): class Meta: model = GoodsCategory fields = '__all__' class CategorySerializer2(serializers.ModelSerializer): sub_category = CategorySerializer3(many=True) # 通过二级分类获取三级分类 class Meta: model = GoodsCategory fields = '__all__' class CategorySerializer(serializers.ModelSerializer): sub_category = CategorySerializer2(many=True) # 通过一级分类获取到二级分类,由于一级分类下有多个二级分类,需要设置many=True class Meta: model = GoodsCategory fields = '__all__' ``` 刷新 http://127.0.0.1:8000/categories/?format=json ![BLOG_20190426_174357_32](/media/blog/images/2019/04/BLOG_20190426_174357_32.png "博客图集BLOG_20190426_174357_32.png") 通过以上修改后,商品列表页也会跟着发生变化,显示的商品分类,会增加子类的显示 ![BLOG_20190426_174332_15](/media/blog/images/2019/04/BLOG_20190426_174332_15.png "博客图集BLOG_20190426_174332_15.png") ### 显示某个分类详情(包含其子类) 类似于获取详情页 修改视图,增加`mixins.RetrieveModelMixin`这样一个mixins ```python class CategoryViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): # 注释很有用,在drf文档中 """ list: 商品分类列表 """ # queryset = GoodsCategory.objects.all() # 取出所有分类,没必要分页,因为分类数据量不大 queryset = GoodsCategory.objects.filter(category_type=1) # 只获取一级分类数据 serializer_class = CategorySerializer # 使用商品类别序列化类,写商品的分类外键已有,直接调用 ``` 这个继承主要是获取某个对象的详情,且这只需要这样写即可。 Restful Api规范中要获取某个对象的详情,只需要在其后面添加某个对象的ID即可。 DRF已经做好了,不需要手动配置获取详情的URL,只需要在ViewSe中继承`mixins.RetrieveModelMixin`即可,其他什么都不需要 访问 http://127.0.0.1:8000/categories/ 可以显示分类列表,加上一级分类ID可以获得某个具体的分类,比如: http://127.0.0.1:8000/categories/121/?format=json ,这个一级分类详情数据主要是显示在前端左侧的分类列表。 ![BLOG_20190426_174322_80](/media/blog/images/2019/04/BLOG_20190426_174322_80.png "博客图集BLOG_20190426_174322_80.png") 由于分类的ViewSet中只会获取一级分类,所以,查询二级分类的ID是没有结果的,比如: http://127.0.0.1:8000/categories/122/?format=json ![BLOG_20190426_174300_43](/media/blog/images/2019/04/BLOG_20190426_174300_43.png "博客图集BLOG_20190426_174300_43.png") ### 在Vue中获取分类API显示前端 vue中存放分类菜单的位置在 src/views/head/head.vue 中 没有数据,按 F12 打开打开Console可以看到错误内容 ``` Access to XMLHttpRequest at 'http://localhost:8000/categories/' from origin 'http://127.0.0.1:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ``` 也就是不允许跨域请求。因为服务器启动的端口是8000,而Vue启动的是8080端口。就需要配置服务器的跨域(前端也可以配置跨域)。 #### 跨域处理django-cors-headers 解决办法: 查看 https://github.com/ottoyiu/django-cors-headers (Django应用程序处理跨源资源共享(CORS)所需的服务器头),下方有说明使用方法 使用pip安装包,`pip install django-cors-headers` 将`corsheaders`应用添加到APPS中 ```python INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # 添加drf应用 'rest_framework', 'django_filters', # Django跨域解决 'corsheaders', # 注册富文本编辑器ckeditor 'ckeditor', # 注册富文本上传图片ckeditor_uploader 'ckeditor_uploader', 'users.apps.UsersConfig', 'goods.apps.GoodsConfig', 'trade.apps.TradeConfig', 'user_operation.apps.UserOperationConfig' ] ``` 还需要添加一个中间件类来监听响应: ```python MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', # corsheaders跨域 'django.middleware.common.CommonMiddleware', # corsheaders跨域 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ``` `CorsMiddleware`应该放在尽可能前面的位置,尤其是在任何能够生成响应的中间件之前,比如Django的`CommonMiddleware`或Whitenoise的`WhiteNoiseMiddleware`。如果不在这之前,它将不能将CORS头添加到这些响应中。 另外,如果正在使用`CORS_REPLACE_HTTPS_REFERER`,那么它应该放在Django的`CsrfViewMiddleware`之前。 全局配置: Django设置中配置中间件的行为。必须将允许执行跨站点请求的主机添加到`CORS_ORIGIN_WHITELIST`,或者将`CORS_ORIGIN_ALLOW_ALL`设置为`True`以允许所有主机。 - `CORS_ORIGIN_ALLOW_ALL`:如果是`True`,白名单将不被使用,所有的连接将被接受。默认值为False。 - `CORS_ORIGIN_WHITELIST`:授权发出跨站点HTTP请求的源主机名列表。值“null”也可以出现在这个列表中,并将与“隐私敏感上下文”中使用的`Origin: null`头匹配,例如当客户机从`file:// domain`运行时。默认为`[]`。 修改 settings.py ,增加CORS配置 ```python # 跨域CORS设置 # CORS_ORIGIN_ALLOW_ALL = False # 默认为False,如果为True则允许所有连接 CORS_ORIGIN_WHITELIST = ( # 配置允许访问的白名单 'localhost:8080', '127.0.0.1:8080', ) ``` 至此,再次刷新前端页面 http://127.0.0.1:8080/#/app/home/index 就正常显示分类数据了 ![BLOG_20190426_174245_83](/media/blog/images/2019/04/BLOG_20190426_174245_83.png "博客图集BLOG_20190426_174245_83.png") #### Vue显示分类菜单流程分析 1. 使用`axios`请求分类数据的api: src/api/api.js ```javascript //获取商品类别信息 export const getCategory = params => { if ('id' in params) { return axios.get(`${local_host}/categories/` + params.id + '/'); } else { return axios.get(`${local_host}/categories/`, params); } }; ``` 在这个代码中,如果没有获取到id,则请求所有的分类,否则请求指定分类id的详情。 2. 从`getCategory`拿到数据之后,取`results`的值(也就是Django Api的是数据部分)赋值给,`allMenuLabel`,默认为`[]`, head/head.vue ```vue getMenu() {//获取菜单 getCategory({ params: {} }).then((response) => { console.log('输出分类列表请求结果:'); console.log(response.data); this.allMenuLabel = response.data.results }) .catch(function (error) { console.log(error); }); }, ``` ![BLOG_20190426_174221_74](/media/blog/images/2019/04/BLOG_20190426_174221_74.png "博客图集BLOG_20190426_174221_74.png") 3. 进行`allMenuLabel`在vue中 for循环的遍历,显示类别的`name` ```vue <div class="main_nav_link" @mouseover="overAllmenu" @mouseout="outAllmenu"> <a class="meunAll">全部商品分类 <i class="iconfont">&#xe643;</i> </a> <div class="main_cata" id="J_mainCata" v-show="showAllmenu"> <ul> <li class="first" v-for="(item,index) in allMenuLabel" @mouseover="overChildrenmenu(index)" @mouseout="outChildrenmenu(index)"> <h3> <router-link :to="'/app/home/list/'+item.id">{{item.name}}</router-link> <!--一级分类名称--> </h3> <div class="J_subCata" id="J_subCata" v-show="showChildrenMenu ===index" style=" left: 215px; top: 0px;"> <div class="J_subView"> <div v-for="second_item in item.sub_category"> <dl> <dt> <router-link :to="'/app/home/list/'+second_item.id">{{second_item.name}}</router-link> <!--二级分类名称--> </dt> <dd> <router-link v-for="third_item in second_item.sub_category" :key="third_item.id" :to="'/app/home/list/'+third_item.id">{{third_item.name}}</router-link> <!--三级分类--> </dd> </dl> <div class="clear"></div> </div> </div> </div> </li> </ul> </div> </div> ``` 遍历三级,显示每一级的分类信息。 #### 分类显示到导航 到目前为止,分类可以正常显示,但是上方的tab没有显示一项,就只有**首页**,对应源码如下, head/head.vue ```vue <ul class="sub_nav cle" id="sub_nav"> <li> <router-link to="/app/home/index">首页</router-link> </li> <template v-for="(item,index) in allMenuLabel"> <li> <div v-if="item.is_tab"> <router-link :to="'/app/home/list/'+item.id">{{item.name}}</router-link> <!--is_tab为True的一级分类--> </div> </li> </template> </ul> ``` 在后台中,所有的分类对象`is_tab`字段都是为`False`,默认不显示在导航中。在Django Admin中设置一些一级分类`is_tab`为`True` 修改 goods/admin.py ,设置商品类别后台 ```python from django.contrib import admin from .models import GoodsCategory, Goods from django.apps import apps @admin.register(GoodsCategory) class GoodsCategoryAdmin(admin.ModelAdmin): list_display = ['name', 'category_type', 'is_tab', 'parent_category'] # 列表页显示 list_display_links = ('name', 'parent_category',) # 列表页外键链接,字段需在list_display中 list_editable = ('is_tab',) # 列表页可编辑 list_filter = ('category_type',) # 列表页可筛选 search_fields = ('name', 'desc') # 列表页可搜索 all_models = apps.get_app_config('goods').get_models() for model in all_models: try: admin.site.register(model) except: pass ``` 访问 http://127.0.0.1:8000/admin/goods/goodscategory/?category_type__exact=1 既可以进行类别过滤 ![BLOG_20190426_174209_73](/media/blog/images/2019/04/BLOG_20190426_174209_73.png "博客图集BLOG_20190426_174209_73.png") 刷新 http://127.0.0.1:8080/#/app/home/index 即可正常显示导航 ![BLOG_20190426_174153_14](/media/blog/images/2019/04/BLOG_20190426_174153_14.png "博客图集BLOG_20190426_174153_14.png")

很赞哦! (0)

文章交流

  • emoji
0人参与,0条评论

当前用户

未登录,点击   登录

站点信息

  • 建站时间:网站已运行2076天
  • 系统信息:Linux
  • 后台程序:Python: 3.8.10
  • 网站框架:Django: 3.2.6
  • 文章统计:256 篇
  • 文章评论:60 条
  • 腾讯分析网站概况-腾讯分析
  • 百度统计网站概况-百度统计
  • 公众号:微信扫描二维码,关注我们
  • QQ群:QQ加群,下载网站的学习源码
返回
顶部
标题 换行 登录
网站