(项目)生鲜超市(四)
五、商品列表页面
1、Django的view实现商品列表页面
为了区分django的view和django rest framework的view,在goods下面新建view_base.py文件,该项目采用前后端分离,所以和模板技术不一样返回的是模本文件,现在给前端返回的必须是json数据:
import json from django.views.generic import View from django.http import HttpResponse from goods.models import Goods class GoodsListView(View): """商品列表页""" def get(self, request): json_list = [] # 获取所有的商品 all_goods = Goods.objects.all() for good in all_goods: json_dict = {} # 将商品信息一字典的形式存储,然后添加到json_list中 json_dict['name'] = good.name json_dict['category'] = good.category.name json_dict['market_price'] = good.market_price json_list.append(json_dict) # 返回json数据 return HttpResponse(json.dumps(json_list), content_type='application/json')
配置url:
1 urlpatterns = [ 2 path('goods/', GoodsListView.as_view(), name='goods-list'), # 商品列表页面 3 ]
现在访问http://127.0.0.1:8000/goods/即可看到返回的数据:
当数据的字段比较多时,一个字段一个字段的提取很麻烦,可以用model_to_dict方法,将model整个转化为dict:
1 import json 2 3 from django.views.generic import View 4 from django.http import HttpResponse 5 from django.forms.models import model_to_dict 6 7 from goods.models import Goods 8 9 10 class GoodsListView(View): 11 """商品列表页""" 12 def get(self, request): 13 json_list = [] 14 # 获取所有的商品 15 all_goods = Goods.objects.all() 16 17 # for good in all_goods: 18 # json_dict = {} 19 # # 将商品信息一字典的形式存储,然后添加到json_list中 20 # json_dict['name'] = good.name 21 # json_dict['category'] = good.category.name 22 # json_dict['market_price'] = good.market_price 23 # json_list.append(json_dict) 24 25 for good in all_goods: 26 json_dict = model_to_dict(good) 27 json_list.append(json_dict) 28 29 # 返回json数据 30 return HttpResponse(json.dumps(json_list), content_type='application/json')
现在去访问http://127.0.0.1:8000/goods/会出现问题,ImageFieldFile和add_time字段不能序列化:
那么如何才能将所有的字段序列化呢?现在就可以用到django的serializers来序列化:
1 import json 2 3 from django.views.generic import View 4 from django.http import HttpResponse, JsonResponse 5 from django.forms.models import model_to_dict 6 from django.core import serializers 7 8 from goods.models import Goods 9 10 11 class GoodsListView(View): 12 """商品列表页""" 13 def get(self, request): 14 json_list = [] 15 # 获取所有的商品 16 all_goods = Goods.objects.all() 17 18 # for good in all_goods: 19 # json_dict = {} 20 # # 将商品信息一字典的形式存储,然后添加到json_list中 21 # json_dict['name'] = good.name 22 # json_dict['category'] = good.category.name 23 # json_dict['market_price'] = good.market_price 24 # json_list.append(json_dict) 25 26 # for good in all_goods: 27 # json_dict = model_to_dict(good) 28 # json_list.append(json_dict) 29 # 30 # # 返回json数据 31 # return HttpResponse(json.dumps(json_list), content_type='application/json') 32 33 json_data = serializers.serialize('json', all_goods) 34 json_data = json.loads(json_data) 35 return JsonResponse(json_data, safe=False)
现在访问http://127.0.0.1:8000/goods/即可看到已经将model中的所有字段序列化:
django的serializers虽然可以很简单的将所有字段序列化,但是缺点也很明显:
- 字段是定死的,不能灵活去序列化指定的字段
- 从上面的截图可以看出,图片保存的是相对地址,还需要手动补全路径
那么如何避免这些问题呢,现在就开始进行django rest framework的使用了。
2、drf的APIview实现商品列表页面
首先在虚拟环境中安装两个包:
- pip install coreapi(drf的文档支持)
- pip install django-guardian(drf对象级别的权限支持)
然后配置drf文档的url:
1 from rest_framework.documentation import include_docs_urls 2 3 urlpatterns = [ 4 path('docs',include_docs_urls(title='倍思乐接口文档')), 5 ]
之前在settings.py中的INSTALLED_APPS中已经注册过rest_framework,如果没有注册,一定要注册进去。
然后配置rest_framework的url:
1 urlpatterns = [ 2 path('api-auth/',include('rest_framework.urls')), 3 ]
现在使用drf的序列化来实现商品列表页,在goods下新建serializers.py文件:
1 from rest_framework import serializers 2 3 4 class GoodsSerializer(serializers.Serializer): 5 name = serializers.CharField(required=True, max_length=100) 6 click_num = serializers.IntegerField(default=0) 7 goods_front_image = serializers.ImageField()
然后在goods/views.py中编写商品列表页面的接口:
1 from django.shortcuts import render 2 from rest_framework.views import APIView 3 from rest_framework.response import Response 4 5 from .models import Goods 6 from .serializers import GoodsSerializer 7 8 # Create your views here. 9 10 11 class GoodsListView(APIView): 12 """商品列表页""" 13 14 def get(self, request, format=None): 15 # 获取所有商品 16 goods = Goods.objects.all() 17 18 # 序列化 19 goods_serializer = GoodsSerializer(goods, many=True) 20 21 return Response(goods_serializer.data)
注意要修改之前的url。然后访问http://127.0.0.1:8000/goods/:
还可以通过Modelserializer来进行序列化,上面是通过Serializer来实现的,需要自己手动去添加序列化的字段,现在使用Modelserializer会更加方便,直接用__all__就可以将字段全部序列化:
1 from rest_framework import serializers 2 3 from .models import Goods 4 5 6 # class GoodsSerializer(serializers.Serializer): 7 # name = serializers.CharField(required=True, max_length=100) 8 # click_num = serializers.IntegerField(default=0) 9 # goods_front_image = serializers.ImageField() 10 11 12 class GoodsSerializer(serializers.ModelSerializer): 13 class Meta: 14 model = Goods 15 fields = '__all__'
上面的截图可以看出,category只显示了id,Serializer还可以嵌套去使用,覆盖外键字段:
class CategorySerializer(serializers.ModelSerializer): class Meta: model = GoodsCategory fields = '__all__' class GoodsSerializer(serializers.ModelSerializer): # 覆盖外键字段 category = CategorySerializer() class Meta: model = Goods fields = '__all__'
3、drf的GenericView实现商品列表页面
GenericView继承APIview,封装了很多方法,比APIview更加好用,而且ListModelMixin里面list方法帮我们做好了分页和序列化的功能,只要调用即可:
1 class GoodsListView(mixins.ListModelMixin, generics.GenericAPIView): 2 """商品列表页面""" 3 4 # 查询集,查询所有的商品 5 queryset = Goods.objects.all() 6 7 # 序列化 8 serializer_class = GoodsSerializer 9 10 def get(self, request, *args, **kwargs): 11 return self.list(request, *args, **kwargs)
上面的代码可以直接继承ListAPIView,这个类直接继承了mixins.ListModelMixin和generics.GenericAPIView,并且写好了get方法,看源码:
1 class GoodsListView(generics.ListAPIView): 2 """商品列表页面""" 3 4 queryset = Goods.objects.all() 5 serializer_class = GoodsSerializer
现在之后三行就将数据返回了。
4、分页功能
在rest_framework的源码文件中默认是没有开启分页功能的,需要自己在settings.py中配置:
1 # rest_framework分页 2 REST_FRAMEWORK = { 3 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 4 'PAGE_SIZE': 10, 5 }
分页也可以自定义,在views.py中自定义分页信息:
1 class GoodsPagination(PageNumberPagination): 2 """商品自定义分页""" 3 4 page_size = 10 # 每页显示个数 5 page_size_query_param = 'page_size' # 动态改变每页显示的个数 6 page_query_param = 'page' # 页码参数 7 max_page_size = 100 # 最多显示页数 8 9 10 class GoodsListView(generics.ListAPIView): 11 """商品列表页面""" 12 13 queryset = Goods.objects.all() 14 serializer_class = GoodsSerializer 15 16 # 分页 17 pagination_class = GoodsPagination
现在在settings.py中的配置就可以注释掉了:
5、drf的viewsets和router完成商品列表页面
1 class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): 2 """商品列表页面""" 3 4 pagination_class = GoodsPagination 5 queryset = Goods.objects.all().order_by('id') # 必须定义一个默认的排序,否则会报错 6 serializer_class = GoodsSerializer
通过router注册url:
1 from goods.views import GoodsListViewSet 2 from rest_framework.routers import DefaultRouter 3 4 router = DefaultRouter() 5 6 7 router.register(r'goods', GoodsListViewSet) # 商品列表页 8 9 urlpatterns = [ 11 re_path('^', include(router.urls)), # 所有接口url 12 ]
6、drf的APIView、GenericView、viewsets和router的原理分析
GenericViewSet是最高的一层,往下依次是GenericAPIView、APIView和django的View,这些view的功能不同,主要体现在mixin的存在,mixins总共有以下五种:
- CreateModelMixin
- ListModelMixin
- UpdateModelMixin
- RetrieveModelMixin
- DestoryModelMixin
以上面的ListModelMixin为例,继承它之后,就可以将get方法和商品的列表关联起来,还有其中的分页功能。
一般的话都是用viewsets,ViewSet类与View类几乎是相同的,其提供的是read或update这些操作,而不是get或put等HTTP动作。同时,ViewSet为我们提供了默认的URL结构, 使得我们能更专注于API本身。
Router提供了一种简单,快速,集成的方式来定义一系列的urls。
7、drf的过滤功能
将django_filters注册到app中:
1 INSTALLED_APPS = [ 2 'django_filters', 3 ]
在goods下新建filter.py文件,自定义一个过滤器:
1 import django_filters 2 3 from .models import Goods 4 5 6 class GoodsFilter(django_filters.rest_framework.FilterSet): 7 """商品过滤""" 8 9 # name是要过滤的字段,lookup是执行的行为 10 price_min = django_filters.NumberFilter(field_name="shop_price", lookup_expr='gte') 11 price_max = django_filters.NumberFilter(field_name="shop_price", lookup_expr='lte') 12 13 class Meta: 14 model = Goods 15 fields = ['price_min', 'price_max']
然后在商品列表接口中增加过滤功能:
1 class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): 2 """商品列表页面""" 3 4 pagination_class = GoodsPagination 5 queryset = Goods.objects.all().order_by('id') # 必须定义一个默认的排序,否则会报错 6 serializer_class = GoodsSerializer 7 filter_backends = (DjangoFilterBackend,) 8 9 # 自定义过滤类 10 filter_class = GoodsFilter
8、drf的搜索和排序
在商品列表接口中完善搜索功能:
1 class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): 2 """商品列表页面""" 3 4 pagination_class = GoodsPagination 5 queryset = Goods.objects.all().order_by('id') # 必须定义一个默认的排序,否则会报错 6 serializer_class = GoodsSerializer 7 filter_backends = (DjangoFilterBackend, filters.SearchFilter) 8 9 # 自定义过滤类 10 filter_class = GoodsFilter 11 12 # 搜索,=name表示精确搜索,也可以使用正则 13 search_fields = ('=name', 'goods_brief')
完善排序功能:
1 class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): 2 """商品列表页面""" 3 4 pagination_class = GoodsPagination 5 queryset = Goods.objects.all().order_by('id') # 必须定义一个默认的排序,否则会报错 6 serializer_class = GoodsSerializer 7 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) 8 9 # 自定义过滤类 10 filter_class = GoodsFilter 11 12 # 搜索,=name表示精确搜索,也可以使用正则 13 search_fields = ('=name', 'goods_brief') 14 15 # 排序 16 ordering_fields = ('sold_num', 'add_time')