五、商品列表页
5.1.django的view实现商品列表页
(1)goods/view_base.py
在goods文件夹下面新建view_base.py,为了区分django和django rest framework的view
利用Django的view实现返回json数据
# goods/view_base.py from django.views.generic import View from goods.models import Goods class GoodsListView(View): def get(self,request): #通过django的view实现商品列表页 json_list = [] #获取所有商品 goods = Goods.objects.all() for good in goods: json_dict = {} #获取商品的每个字段,键值对形式 json_dict['name'] = good.name json_dict['category'] = good.category.name json_dict['market_price'] = good.market_price json_list.append(json_dict) from django.http import HttpResponse import json #返回json,一定要指定类型content_type='application/json' return HttpResponse(json.dumps(json_list),content_type='application/json')
(2)MxShop/urls.py
from goods.view_base import GoodsListView urlpatterns = [ #商品列表页 path('goods/',GoodsListView.as_view(),name='goods-list') ]
访问http://127.0.0.1:8000/goods/ 可以获取商品列表信息的json数据
5.2.django的serializer序列化model
(1)model_to_dict
当字段比较多时,一个字段一个字段的提取很麻烦,可以用model_to_dict,将model整个转化为dict
# goods/view_base.py from django.views.generic import View from goods.models import Goods class GoodsListView(View): def get(self,request): #通过django的view实现商品列表页 json_list = [] #获取所有商品 goods = Goods.objects.all() # for good in goods: # json_dict = {} # #获取商品的每个字段,键值对形式 # json_dict['name'] = good.name # json_dict['category'] = good.category.name # json_dict['market_price'] = good.market_price # json_list.append(json_dict) from django.forms.models import model_to_dict for good in goods: json_dict = model_to_dict(good) json_list.append(json_dict) from django.http import HttpResponse import json #返回json,一定要指定类型content_type='application/json' return HttpResponse(json.dumps(json_list),content_type='application/json')
但是这样有个问题,就是ImageFieldFile 和add_time字段不能序列化
如何才能将所有字段序列化呢?就要用到django的serializers
(2)django serializer的用法
# goods/view_base.py from django.views.generic import View from goods.models import Goods class GoodsListView(View): def get(self,request): #通过django的view实现商品列表页 json_list = [] #获取所有商品 goods = Goods.objects.all() # for good in goods: # json_dict = {} # #获取商品的每个字段,键值对形式 # json_dict['name'] = good.name # json_dict['category'] = good.category.name # json_dict['market_price'] = good.market_price # json_list.append(json_dict) import json from django.core import serializers from django.http import JsonResponse json_data = serializers.serialize('json',goods) json_data = json.loads(json_data) #In order to allow non-dict objects to be serialized set the safe parameter to False. return JsonResponse(json_data,safe=False)
django的serializer虽然可以很简单实现序列化,但是有几个缺点
- 字段序列化定死的,要想重组的话非常麻烦
- 从上面截图可以看出来,images保存的是一个相对路径,我们还需要补全路径,而这些drf都可以帮助我们做到
以上写了这么多只是为了引入django rest framework和简单介绍django的序列化用法,下面就是重点讲解django rest framework了
5.3.APIview方式实现商品列表页
(1)安装
- pip install coreapi drf的文档支持
- pip install django-guardian drf对象级别的权限支持
(2)配置def文档的url
MxShop/urls.py
from rest_framework.documentation import include_docs_urls urlpatterns = [ #drf文档,title自定义 path('docs',include_docs_urls(title='仙剑奇侠传')), ]
(3)配置rest_framework
settings.py中添加
INSTALLED_APPS = [ 'rest_framework', ]
MxShop/urls.py
urlpatterns = [ path('api-auth/',include('rest_framework.urls')), ]
(4)goods文件夹下面新建serializers.py
用drf的序列化实现商品列表页展示,代码如下:
# goods/serializers.py from rest_framework import serializers class GoodsSerializer(serializers.Serializer): name = serializers.CharField(required=True,max_length=100) click_num = serializers.IntegerField(default=0) goods_front_image = serializers.ImageField()
(5)goods/views.py
# googd/views.py from rest_framework.views import APIView from goods.serializers import GoodsSerializer from .models import Goods from rest_framework.response import Response class GoodsListView(APIView): ''' 商品列表 ''' def get(self,request,format=None): goods = Goods.objects.all() goods_serialzer = GoodsSerializer(goods,many=True) return Response(goods_serialzer.data)
5.4.drf的Modelserializer实现商品列表页
上面是用Serializer实现的,需要自己手动添加字段,如果用Modelserializer,会更加的方便,直接用__all__就可以全部序列化
# goods/serializers.py from rest_framework import serializers from .models import Goods #Serializer实现商品列表页 # class GoodsSerializer(serializers.Serializer): # name = serializers.CharField(required=True,max_length=100) # click_num = serializers.IntegerField(default=0) # goods_front_image = serializers.ImageField() #ModelSerializer实现商品列表页 class GoodsSerializer(serializers.ModelSerializer): class Meta: model = Goods fields = '__all__'
category只显示分类的id,Serialzer还可以嵌套使用,覆盖外键字段
# goods/serializers.py from rest_framework import serializers from .models import Goods,GoodsCategory #Serializer实现商品列表页 # class GoodsSerializer(serializers.Serializer): # name = serializers.CharField(required=True,max_length=100) # click_num = serializers.IntegerField(default=0) # goods_front_image = serializers.ImageField() class CategorySerializer(serializers.ModelSerializer): class Meta: model = GoodsCategory fields = "__all__" #ModelSerializer实现商品列表页 class GoodsSerializer(serializers.ModelSerializer): #覆盖外键字段 category = CategorySerializer() class Meta: model = Goods fields = '__all__'
5.5.GenericView实现商品列表页
(1)mixins和generic一起用用
GenericAPIView继承APIView,封装了很多方法,比APIView功能更强大
用的时候需要定义queryset和serializer_class
GenericAPIView里面默认为空
- queryset = None
- serializer_class = None
ListModelMixin里面list方法帮我们做好了分页和序列化的工作,只要调用就好了
实现如下:
from goods.serializers import GoodsSerializer from .models import Goods from rest_framework.response import Response from rest_framework import mixins from rest_framework import generics class GoodsListView(mixins.ListModelMixin,generics.GenericAPIView): '商品列表页' queryset = Goods.objects.all() serializer_class = GoodsSerializer def get(self,request,*args,**kwargs): return self.list(request,*args,**kwargs)
上面的代码优化,可以直接继承ListAPIView,ListAPIView主要做了两件事:
- ListAPIView(mixins.ListModelMixin,GenericAPIView) 继承了这两个类
- 写好了get方法
我们要获取商品列表页的信息,只要写三行代码就可以了
class GoodsListView(generics.ListAPIView): '商品列表页' queryset = Goods.objects.all() serializer_class = GoodsSerializer
5.6.添加分页功能
先看rest_framework/settings.py源码,里面可以找到如何配置:比如认证、权限和分页等等
添加分页功能,配置如下:
REST_FRAMEWORK = { #分页 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', #每页显示的个数 'PAGE_SIZE': 10, }
自定也分页功能
from rest_framework.pagination import PageNumberPagination class GoodsPagination(PageNumberPagination): ''' 商品列表自定义分页 ''' #默认每页显示的个数 page_size = 10 #可以动态改变每页显示的个数 page_size_query_param = 'page_size' #页码参数 page_query_param = 'page' #最多能显示多少页 max_page_size = 100 class GoodsListView(generics.ListAPIView): '商品列表页' pagination_class = GoodsPagination #分页 queryset = Goods.objects.all() serializer_class = GoodsSerializer
settings.py里面就不用设置了
5.7.viewsets和router完成商品列表页
主要用到viewsets中的GenericViewSet
再看下ViewSerMixin源码
ViewSets和Routers结合使用
MxShop/yrls.py
from goods.views import GoodsListViewSet from rest_framework.routers import DefaultRouter router = DefaultRouter() #配置goods的url router.register(r'goods', GoodsListViewSet) urlpatterns = [ #商品列表页 re_path('^', include(router.urls)), ]
views.py
必须定义一个默认的排序方式
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet): '商品列表页' # 分页 pagination_class = GoodsPagination #这里必须要定义一个默认的排序,否则会报错 queryset = Goods.objects.all().order_by('id') serializer_class = GoodsSerializer
5.8.drf的APIView、GenericView、viewsets和router的原理分析
genericViewSet 是最高的一层
往下
GenericViewSet(viewsets) ----drf
GenericAPIView ---drf
APIView ---drf
View ----django
这些view功能的不同,主要的是有mixin的存在
mixins总共有五种:
CreateModelMixin
ListModelMixin
UpdateModelMixin
RetrieveModelMixin
DestoryModelMixin
以ListModelMixin为例:
如果不继承ListModelMixin的话,就无法将get和商品的列表关联起来,另外还有其中的分页等等,都无法实现。
还有其它几个mixin(增删改查局部),这些功能都是mixin做的
我们一般都是用viewsets
ViewSet类与View类其实几乎是相同的,但提供的是read或update这些操作,而不是get或put 等HTTP动作。同时,ViewSet为我们提供了默认的URL结构, 使得我们能更专注于API本身。
Router提供了一种简单,快速,集成的方式来定义一系列的urls
5.9.drf的request和response介绍
REST framework 的 Request
类扩展与标准的 HttpRequest
,并做了相应的增强,比如更加灵活的请求解析(request parsing)和认证(request authentication)。
REST framwork 的 Request
对象提供了灵活的请求解析,允许你使用 JSON data 或 其他 media types 像通常处理表单数据一样处理请求。
request.data
返回请求主题的解析内容。这跟标准的 request.POST
和 request.FILES
类似,并且还具有以下特点:
- 包括所有解析的内容,文件(file) 和 非文件(non-file inputs)。
- 支持解析
POST
以外的 HTTP method , 比如PUT
,PATCH
。 - 更加灵活,不仅仅支持表单数据,传入同样的 JSON 数据一样可以正确解析,并且不用做额外的处理(意思是前端不管提交的是表单数据,还是 JSON 数据,
.data
都能够正确解析)。
.data 具体操作,以后再说~
request.query_params
等同于 request.GET
,不过其名字更加容易理解。
为了代码更加清晰可读,推荐使用 request.query_params
,而不是 Django 中的 request.GET
,这样那够让你的代码更加明显的体现出 ----- 任何 HTTP method 类型都可能包含查询参数(query parameters),而不仅仅只是 'GET' 请求。
.parser
APIView
类或者 @api_view
装饰器将根据视图上设置的 parser_classes
或 settings
文件中的 DEFAULT_PARSER_CLASSES
设置来确保此属性(.parsers
)自动设置为 Parser
实例列表。
通常不需要关注该属性......
如果你非要看看它里面是什么,可以打印出来看看,大概长这样:
[<rest_framework.parsers.JSONParser object at 0x7fa850202d68>, <rest_framework.parsers.FormParser object at 0x7fa850202be0>, <rest_framework.parsers.MultiPartParser object at 0x7fa850202860>]
包含三个解析器 JSONParser
,FormParser
,MultiPartParser
。
注意: 如果客户端发送格式错误的内容,则访问
request.data
可能会引发ParseError
。默认情况下, REST framework 的APIView
类或者@api_view
装饰器将捕获错误并返回400 Bad Request
响应。 如果客户端发送的请求内容无法解析(不同于格式错误),则会引发UnsupportedMediaType
异常,默认情况下会被捕获并返回415 Unsupported Media Type
响应。
Responses
与基本的 HttpResponse 对象不同,TemplateResponse 对象保留了视图提供的用于计算响应的上下文的详细信息。直到需要时才会计算最终的响应输出,也就是在后面的响应过程中进行计算。 — Django 文档
REST framework 通过提供一个 Response
类来支持 HTTP 内容协商,该类允许你根据客户端请求返回不同的表现形式(如: JSON ,HTML 等)。
Response
类的子类是 Django 的 SimpleTemplateResponse
。Response
对象使用数据进行初始化,数据应由 Python 对象(native Python primitives)组成。然后 REST framework 使用标准的 HTTP 内容协商来确定它应该如何渲染最终响应的内容。
当然,您也可以不使用 Response
类,直接返回常规 HttpResponse
或 StreamingHttpResponse
对象。 使用 Response
类只是提供了一个更好的交互方式,它可以返回多种格式。
除非由于某种原因需要大幅度定制 REST framework ,否则应该始终对返回 Response
对象的视图使用 APIView
类或 @api_view
装饰器。这样做可以确保视图执行内容协商,并在视图返回之前为响应选择适当的渲染器。
与普通 HttpResponse
对象不同,您不会使用渲染的内容实例化 Response
对象。相反,您传递的是未渲染的数据,可能包含任何 Python 对象。
由于 Response
类使用的渲染器不能处理复杂的数据类型(比如 Django 的模型实例),所以需要在创建 Response
对象之前将数据序列化为基本的数据类型。
你可以使用 REST framework 的 Serializer
类来执行序列化的操作,也可以用自己的方式来序列化。
构造方法: Response(data, status=None, template_name=None, headers=None, content_type=None)
参数:
data
: 响应的序列化数据。status
: 响应的状态代码。默认为200。template_name
: 选择HTMLRenderer
时使用的模板名称。headers
: 设置 HTTP header,字典类型。content_type
: 响应的内容类型,通常渲染器会根据内容协商的结果自动设置,但有些时候需要手动指定。
还没有渲染,但已经序列化的响应数据。
状态码
将会返回的响应内容,必须先调用 .render()
方法,才能访问 .content
。
只有在 response 的渲染器是 HTMLRenderer
或其他自定义模板渲染器时才需要提供。
用于将会返回的响应内容的渲染器实例。
从视图返回响应之前由 APIView
或 @api_view
自动设置。
内容协商阶段选择的媒体类型。
从视图返回响应之前由 APIView
或 @api_view
自动设置。
.renderer_context
将传递给渲染器的 .render()
方法的附加的上下文信息字典。
从视图返回响应之前由 APIView
或 @api_view
自动设置。
Response
类扩展于 SimpleTemplateResponse
,并且响应中也提供了所有常用的属性和方法。例如,您可以用标准方式在响应中设置 header:
response = Response() response['Cache-Control'] = 'no-cache'
.render()
与其他任何 TemplateResponse
一样,调用此方法将响应的序列化数据呈现为最终响应内容。响应内容将设置为在 accepted_renderer
实例上调用 .render(data,accepted_media_type,renderer_context)
方法的结果。
通常不需要自己调用 .render()
,因为它是由 Django 处理的。
5.10.drf的过滤
drf的filter用法 http://www.django-rest-framework.org/api-guide/filtering/
(1)添加到app里面
INSTALLED_APPS = [ 'django_filters', ]
(2)新建filter.py
自定义一个过滤器
# goods/filters.py import django_filters from .models import Goods class GoodsFilter(django_filters.rest_framework.FilterSet): ''' 商品过滤的类 ''' #两个参数,name是要过滤的字段,lookup是执行的行为,‘小与等于本店价格’ price_min = django_filters.NumberFilter(name="shop_price", lookup_expr='gte') price_max = django_filters.NumberFilter(name="shop_price", lookup_expr='lte') class Meta: model = Goods fields = ['price_min', 'price_max']
(3)views.py
from .filters import GoodsFilter
from django_filters.rest_framework import DjangoFilterBackend
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet): '商品列表页' #这里必须要定义一个默认的排序,否则会报错 queryset = Goods.objects.all().order_by('id') # 分页 pagination_class = GoodsPagination serializer_class = GoodsSerializer filter_backends = (DjangoFilterBackend,) # 设置filter的类为我们自定义的类 filter_class = GoodsFilter
5.11.drf的搜索和排序
添加搜索功能
搜索的字段可以使用正则表达式,更加的灵活
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet): '商品列表页' #这里必须要定义一个默认的排序,否则会报错 queryset = Goods.objects.all().order_by('id') # 分页 pagination_class = GoodsPagination serializer_class = GoodsSerializer filter_backends = (DjangoFilterBackend,filters.SearchFilter) # 设置filter的类为我们自定义的类 filter_class = GoodsFilter #搜索,=name表示精确搜索,也可以使用各种正则表达式 search_fields = ('=name','goods_brief')
添加排序功能
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet): '商品列表页' #这里必须要定义一个默认的排序,否则会报错 queryset = Goods.objects.all() # 分页 pagination_class = GoodsPagination #序列化 serializer_class = GoodsSerializer filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter) # 设置filter的类为我们自定义的类 #过滤 filter_class = GoodsFilter #搜索,=name表示精确搜索,也可以使用各种正则表达式 search_fields = ('=name','goods_brief') #排序 ordering_fields = ('sold_num', 'add_time')
所有代码
# googd/views.py
from rest_framework.views import APIView
from goods.serializers import GoodsSerializer
from .models import Goods
from rest_framework.response import Response
from rest_framework import mixins
from rest_framework import generics
from rest_framework.pagination import PageNumberPagination
from rest_framework import viewsets
from .filters import GoodsFilter
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
class GoodsPagination(PageNumberPagination):
'''
商品列表自定义分页
'''
#默认每页显示的个数
page_size = 10
#可以动态改变每页显示的个数
page_size_query_param = 'page_size'
#页码参数
page_query_param = 'page'
#最多能显示多少页
max_page_size = 100
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet):
'商品列表页'
#这里必须要定义一个默认的排序,否则会报错
queryset = Goods.objects.all()
# 分页
pagination_class = GoodsPagination
#序列化
serializer_class = GoodsSerializer
filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter)
# 设置filter的类为我们自定义的类
#过滤
filter_class = GoodsFilter
#搜索,=name表示精确搜索,也可以使用各种正则表达式
search_fields = ('=name','goods_brief')
#排序
ordering_fields = ('sold_num', 'add_time')
views.py
# googd/views.py from rest_framework.views import APIView from goods.serializers import GoodsSerializer from .models import Goods from rest_framework.response import Response from rest_framework import mixins from rest_framework import generics from rest_framework.pagination import PageNumberPagination from rest_framework import viewsets from .filters import GoodsFilter from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters class GoodsPagination(PageNumberPagination): ''' 商品列表自定义分页 ''' #默认每页显示的个数 page_size = 10 #可以动态改变每页显示的个数 page_size_query_param = 'page_size' #页码参数 page_query_param = 'page' #最多能显示多少页 max_page_size = 100 class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet): '商品列表页' #这里必须要定义一个默认的排序,否则会报错 queryset = Goods.objects.all() # 分页 pagination_class = GoodsPagination #序列化 serializer_class = GoodsSerializer filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter) # 设置filter的类为我们自定义的类 #过滤 filter_class = GoodsFilter #搜索,=name表示精确搜索,也可以使用各种正则表达式 search_fields = ('=name','goods_brief') #排序 ordering_fields = ('sold_num', 'add_time')
# googd/views.py
from rest_framework.views import APIView
from goods.serializers import GoodsSerializer
from .models import Goods
from rest_framework.response import Response
from rest_framework import mixins
from rest_framework import generics
from rest_framework.pagination import PageNumberPagination
from rest_framework import viewsets
from .filters import GoodsFilter
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
class GoodsPagination(PageNumberPagination):
'''
商品列表自定义分页
'''
#默认每页显示的个数
page_size = 10
#可以动态改变每页显示的个数
page_size_query_param = 'page_size'
#页码参数
page_query_param = 'page'
#最多能显示多少页
max_page_size = 100
class GoodsListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet):
'商品列表页'
#这里必须要定义一个默认的排序,否则会报错
queryset = Goods.objects.all()
# 分页
pagination_class = GoodsPagination
#序列化
serializer_class = GoodsSerializer
filter_backends = (DjangoFilterBackend,filters.SearchFilter,filters.OrderingFilter)
# 设置filter的类为我们自定义的类
#过滤
filter_class = GoodsFilter
#搜索,=name表示精确搜索,也可以使用各种正则表达式
search_fields = ('=name','goods_brief')
#排序
ordering_fields = ('sold_num', 'add_time')
views.py
# MxShop/urls.py __author__ = 'derek' from django.urls import path,include,re_path import xadmin from django.views.static import serve from MxShop.settings import MEDIA_ROOT # from goods.view_base import GoodsListView from rest_framework.documentation import include_docs_urls from goods.views import GoodsListViewSet from rest_framework.routers import DefaultRouter router = DefaultRouter() #配置goods的url router.register(r'goods', GoodsListViewSet) urlpatterns = [ path('xadmin/', xadmin.site.urls), path('api-auth/',include('rest_framework.urls')), path('ueditor/',include('DjangoUeditor.urls' )), #文件 path('media/<path:path>',serve,{'document_root':MEDIA_ROOT}), #drf文档,title自定义 path('docs',include_docs_urls(title='仙剑奇侠传')), #商品列表页 re_path('^', include(router.urls)), ]