• 5- vue django restful framework 打造生鲜超市 -完成商品列表页(上)


    使用Python3.6与Django2.0.2(Django-rest-framework)以及前端vue开发的前后端分离的商城网站

    项目支持支付宝支付(暂不支持微信支付),支持手机短信验证码注册, 支持第三方登录。集成了sentry错误监控系统。

    本小节内容: Django原生以及使用drf 完成 商品列表页

    django的view实现商品列表页

    本章节很重要。

    通过Django的fbv cbv (class base view)都可以实现。

    更建议通过基于class的view编码,面向对象。

    实现json返回。通过商品列表页学习大多数drf知识点

    1. 配置url
    商品列表页
    path('goods/', GoodsListView.as_view(),name="goods-list"),
    
    1. goods中新建一个view_base 来实现一个只通过Django实现的json返回。

    查看Django的开发文档可以看到提供了很多view来减少我们的代码量

    class GoodsListView(View):
        def get(self, request):
            """
            通过django的view实现商品列表页
            """
            json_list = []
            goods = Goods.objects.all()[:10]
    
            from django.forms.models import model_to_dict
            for good in goods:
                json_dict = model_to_dict(good)
                json_list.append(json_dict)
    
            import json
            from django.core import serializers
            json_data = serializers.serialize('json', goods)
            json_data = json.loads(json_data)
            from django.http import HttpResponse, JsonResponse
            return JsonResponse(json_data, safe=False)
    

    model_to_dict 将model转换为字典,不用一个字段一个字段提取。

    • images field和 datetime直接dumps会出错
    • serializers 专门用于序列化,有了这个序列化,其实上面的model_to_dict都不用做了
    moudle not callable
    

    说明这是一个moudle我们不能直接调用,而应该进一步写明调用其中哪个方法。

     
    mark
    1. 最基本方法问题,字段被一个一个的序列化。字段很多会很麻烦
      # 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_dict["add_time"] = good.add_time
            #     json_list.append(json_dict)
    
    1. 如果将addtime加入会报错,json.dumps无法自己完成。
    TypeError: Object of type 'add_time' is not JSON serializable
    

    推荐阅读:

    https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/00138683221577998e407bb309542d9b6a68d9276bc3dbe000

    Django本身的实现其实已经蛮简单了,那我们为啥要用drf

    敲重点!!!

    images这个图片保存的是一个相对路径,而我们需要加上前面的前缀。

    这个工作drf可以完成补前缀工作。

    字段序列化方式被定死了,重组麻烦。

    文档生成,输入检测(你是放在request body还表单过来的)

    使用drf完成商品列表页

    商品列表页过基础知识

    http://www.django-rest-framework.org/

    强大的,灵活的,api

    • Web browsable API
    • Authentication policies 认证
    • Serialization that supports both ORM and non-ORM data sources.

    序列化ORM 和 非ORM的数据源

    regular function-based views 我们是用的cbv

     
    mark

    安装

    coreapi (1.32.0+) - Schema generation support.
    django-guardian (1.1.1+) - Object level permissions support.
    

    对象级别的权限支持,coreapi支持文档

    安装时报出utf-8 decode错误

    修改虚拟环境中的

    D:CodeSpacePythonEnvsmxshop36Libsite-packagespipcompat
    

    大约75行

     
    mark

    如果出现错误,把这个地方改为gbk

    引入我们的文档。

        # 自动化文档,1.11版本中注意此处前往不要加$符号
        path('docs/', include_docs_urls(title='mtianyan生鲜超市文档'))
    

    配置好了只要运行不报错验证成功。

    Add 'rest_framework' to your INSTALLED_APPS setting.

    INSTALLED_APPS = (
        ...
        'rest_framework',
    )
    
    path('api-auth/', include('rest_framework.urls'))
    
     
    mark

    http://www.django-rest-framework.org/tutorial/3-class-based-views/

    这里面官方给出了一个例子如何简单的写一个class view

    class SnippetList(APIView):
        """
        List all snippets, or create a new snippet.
        """
        def get(self, request, format=None):
            snippets = Snippet.objects.all()
            serializer = SnippetSerializer(snippets, many=True)
            return Response(serializer.data)
    

    将官方示例中的snippets用我们的Goods代替

     
    mark

    可以看到api view也是继承的view在它之上做了很多事。

    from .models import Goods
    

    写明引入当前目录下models中的Goos不容易重名失败。

    可以自定义序列化的类。SnippetSerializer

    modelform 和 form。modelform可以将字段直接转成html

    现在drf里的Serializer是用来取代form开发的。modelfrom是针对html的,
    Serializer是针对json的。

    和之前的form一样。新建一个文件serializers.py

    http://www.django-rest-framework.org/tutorial/1-serialization/

    from rest_framework import serializers
    
    
    class GoodsSerializer(serializers.Serializer):
        name = serializers.CharField(required=True,max_length=100)
        click_num = serializers.IntegerField(default=0)
    

    字段太多了。我们先讲两个。

     
    mark

    因为我们是在用浏览器请求所以Drf会帮你渲染成网页格式

     
    mark

    这就是drf的Web browsable API

    使用浏览器请求会返回html。

     
    mark

    get的时候指明json

     
    mark
     
    mark

    接口的描述我们可以自行定义。

    post过去的数据他能解析的格式。

    因为我们是序列化的Goods,所以我们的Serializer要和goods model中保持一致

    我们在Serializer中加上front image

     
    mark

    可以看到我们返回的json中,image字段全部加上了media前缀

    前缀是通过我们setting的MEDIA_URL 来自动添加的。

    drf的modelSerializer实现商品列表页功能

    在drf的登录系统中user nonetype
    注意检查这里需要返回的是username

        def __str__(self):
            return self.username
    

    之所以可以登录退出,
    是因为这里配置了一个url

        # 调试登录
        path('api-auth/', include('rest_framework.urls'))
    

    goods/views.py添加:

      def post(self, request, format=None):
            serializer = GoodsSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    

    接收前端request数据,交给GoodsSerializer进行验证,验证通过进行保存。

    goods/serializers.py添加:

      def create(self, validated_data):
            """
            Create and return a new `Goods` instance, given the validated data.
            """
            return Goods.objects.create(**validated_data)
    

    有了drf之后,他会把不管是用户GET post Body过来的数据
    都会取到,放到data中。不需要对于get post 等做单独的处理。

    save 会去调用Serializer里的create方法。

    Django有form和modelform。那么Serializer是不是也有他的model

    bingo

    class GoodsSerializer(serializers.ModelSerializer):
        class Meta:
            model = Goods
            # fields = ('category', 'goods_sn', 'name', 'click_num', 'sold_num', 'market_price')
            fields = "__all__"
    
     
    mark

    外键会序列化成id。那我们通过id拿到对应的category

    进行Serializer的嵌套使用。覆盖外键字段

    class CategorySerializer(serializers.ModelSerializer):
        class Meta:
            model = GoodsCategory
            fields = "__all__"
    
    
    class GoodsSerializer(serializers.ModelSerializer):
        category = CategorySerializer()
        
        class Meta:
            model = Goods
            # fields = ('category', 'goods_sn', 'name', 'click_num', 'sold_num', 'market_price')
            fields = "__all__"
    

    GenericView 方式实现商品列表页和分页功能

    使用更加上层的view写起来更简单。

    使用Using mixins 和 generic view

    GenericAPIView是在apiview的基础上加了filter 分页等一堆东西

    我们不用添加createModelMixin是因为我们的商品数据是后台添加的,前台不会提交。

    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)
    

    list函数是在mixin中的,做了分页以及序列化。

    不去重写定义get等http请求的方法默认你不接收这种方法

    虽然上面已经很简单了,但是我们能不能更简单呢。

    查看源码路径: D:/CodeSpace/PythonEnvs/mxshop36/Lib/site-packages/rest_framework

    D:/CodeSpace/PythonEnvs/mxshop36/Lib/site-packages/rest_framework/generics.py

     
    mark

    这些view都是官方提供给我们的view

    • ListAPIview (获取列表)
    • CreateAPiView (创建一个)
    • Retrieve (获取某一条)
    class GoodsListView(ListAPIView):
        """
        商品列表页
        """
        queryset = Goods.objects.all()
        serializer_class = GoodsSerializer
    

    列表页通常都是要分页的,我们如何只通过setting的一个配置完成我们的分页。

    所有关于restframework的配置要写在变量里

    D:/CodeSpace/PythonEnvs/mxshop36/Lib/site-packages/rest_framework/settings.py

    源码中的setting配置

     
    mark

    可以看到已经不提供默认的分页类了。我们需要自己指明

    REST_FRAMEWORK = {
        'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
        'PAGE_SIZE': 10,
    }
    
     
    mark
     
    mark

    返回的数据有了变化。

    有了count next页的url,将之前的数据放到了results中

     
    mark

    很神奇的是图片连域名都加上了。

    问题来了: 这个域名怎么加上的,待探究。

    将整个url提供实际是我们restful api的一个标准

    class GoodsPagination(PageNumberPagination):
        page_size = 12
        page_size_query_param = 'page_size'
        page_query_param = "page"
        max_page_size = 100
    

    最多100个。

    goodslistview中添加

        pagination_class = GoodsPagination
    
     
    mark

    第几页,每一页取多少条都变成了可配置的。

    Viewsets和router完成商品列表页

    from rest_framework import viewsets
    
     
    mark

    可以看到它里面包含的

    class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    

    它的内部之间pass,只是多继承了一个ViewSetMixin

    ViewSetMixin中重写了as_view方法可以让我们的注册url变得更加简单

     
    mark

    在view的基础上设置了很多动作。动态设置Serializer

    class GoodsListView(mixins.ListModelMixin, generics.GenericAPIView):
    

    GenericViewSet只继承了api view,那么原来ListAPIView中的get方法的实现就没有了

    class GoodsListView(mixins.ListModelMixin, viewsets.GenericViewSet):
    

    ViewSets和Routers配套使用。

    1. 为了让我们更好看清我们使用的是viewset的实现.

    goods/views.py

    class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
    

    改名字

    配置我们的url

    goods_list = GoodsListViewSet.as_view({
        'get': 'list',
    })
    

    将get请求绑定到list之上,类似于之前的

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
    

    这样我们就不用自己再去绑定了。

    配置url时就不用再加as_view()了

        # 商品列表页
        path('goods/', goods_list,name="goods-list"),
    

    但是我们可以更厉害一点,直接不用进行这个get 与list的绑定,它自动完成

    我们要介绍的router就是做这个的。

    from rest_framework.routers import DefaultRouter
    
    router = DefaultRouter()
    
    # 配置goods的url 
    router.register(r'goods', GoodsListViewSet)
    

    url中配置

        # router的path路径
        re_path('^', include(router.urls)),
    

    router自动帮我们配置了get 和list,create 和 post的绑定

    对于api view, GenericView viewset 和 router 的原理分析

    理清我们的这些view他们之间的关系,以及listmodelMixin。
    以及这些关系的组合使用,这样才能让我们更清楚什么时候使用哪种。

    1. genericViewSet 是最高的一层
    GenericViewSet(ViewSet) -drf
        GenericAPIView      -drf
            APIView         -drf
                View        -django
    

    这些view之间的差异就引出了drf中另一个核心点mixin

     
    mark

    可以看到源码中的mixin一共有五种

    而各种view的差异,mixin就扮演着重要的角色

    以ListModelMixin为例做区别,如果我们不去继承这个mixin它里面的这些方法的话。
    就无法将get 和 list连接起来。无法连接,那么list中所作的所有功能都不能完成。

    比如其中的

      queryset = self.filter_queryset(self.get_queryset())
      page = self.paginate_queryset(queryset)
            if page is not None:
                serializer = self.get_serializer(page, many=True)
                return self.get_paginated_response(serializer.data)
    
            serializer = self.get_serializer(queryset, many=True)
            return Response(serializer.data)
    

    过滤,分页都将享受不到。

    RetrieveModelMixin对于具体的商品信息进行了获取,序列化。这个在后面的商品详情页会介绍到。

    UpdateModelMixin中对于部分更新还是全部更新进行了判断。

    DestroyModelMixin用来连接我们的delete方法,在我们delete时有一些必要的操作,如设置返回状态204等。

    上述这些功能都是mixin做的,而generic view并没有做。所以drf是通过两者的结合来实现。

    GenericAPIView继承于views.APIView

    Base class for all other generic views.是所有通用视图的基类

    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
    

    在原本的apiview基础上添加了过滤,分页。如果不用genericapiview而用api view这些事情都要我们自己实现。

    配合着genericview 加上mixin就能组合出更加强大的:

     
    mark

    如上图这些分别为某个单独操作设置的apiview

    class RetrieveAPIView(mixins.RetrieveModelMixin,
                          GenericAPIView):
        """
        Concrete view for retrieving a model instance.
        具体的视图 对于 检索一个model实例
        """
        def get(self, request, *args, **kwargs):
            return self.retrieve(request, *args, **kwargs)
    

    get响应的是浏览器发过来的请求,浏览器发过来的请求只有http协议中规定的几种。
    我们将get请求绑定上retrieve方法,就能享受到retrieve方法给我们的好处

    我们可以自己写一个view来继承mixins.RetrieveModelMixin,GenericAPIView 但是我们
    一定要把get 和 retrieve的绑定写上。

    一般我们都会优先考虑下面组合好的这些apiview。还是满足不了要求,那就自己组合,满足这种配置方式就可以了。

    Viewset有哪些好处?

    generic下的各种变种view也是继承了genericAPIView,配合各种mixin进行组合工作。

    它将具体的单个modelmixin(如:mixins.RetrieveModelMixin,)换成了ViewSetMixin

    之前我们需要写函数将get等与retrieve等进行绑定。

    ViewSetMixin的好处就是: 不需要我们通过方法来绑定。但是我们还是需要绑定关系的。

    在url配置的时候进行绑定。

    def as_view(cls, actions=None, **initkwargs)
    

    他重写了as_view,接受参数,传递到对应的method 与 action进行绑定

    goods_list = GoodsListViewSet.as_view({
        'get': 'list',
    })
    

    将本来在代码中的绑定移到url中进行一个绑定的配置。

    我们可以用router进行一个默认的绑定,router中的绑定其实和generic中的差不多

    viewSetmixin还有一个很大的功能:

    def initialize_request(self, request, *args, **kwargs):
           """
            Set the `.action` attribute on the view,
            depending on the request method.
            """
            request = super(ViewSetMixin, self).initialize_request(request, *args, **kwargs)
            method = request.method.lower()
            if method == 'options':
                # This is a special case as we always provide handling for the
                # options method in the base `View` class.
                # Unlike the other explicitly defined actions, 'metadata' is implicit.
                self.action = 'metadata'
            else:
                self.action = self.action_map.get(method)
            return request
    

    为视图绑定动作,依赖于具体的request请求方法。这些action在我们后期进行动态Serializer时有很大好处。

     
    mark



    文章学习来自简书 作者:天涯明月笙
    原文链接:https://www.jianshu.com/p/6a6fa62d8152

  • 相关阅读:
    第04组(64) 团队展示
    结对编程作业
    第一次个人编程作业
    寒假第二份大礼包
    热身礼包初体验,欢欢喜喜过大年
    第08组 Beta冲刺 (1/5)
    第08组 Alpha冲刺 总结
    第五次作业
    第08组 Alpha冲刺 (6/6)
    第08组 Alpha冲刺 (5/6)
  • 原文地址:https://www.cnblogs.com/xinjie57/p/9144769.html
Copyright © 2020-2023  润新知